From 2c3cd5208b7ffa7bb84e3980442233fcd4dc38b2 Mon Sep 17 00:00:00 2001 From: woodser Date: Sun, 10 Nov 2024 11:22:10 -0500 Subject: [PATCH] support USDT-ERC20 and USDT-TRC20 --- .../main/java/haveno/asset/Trc20Token.java | 29 +++++++++++++ .../haveno/asset/tokens/TetherUSDERC20.java | 11 +++++ .../haveno/asset/tokens/TetherUSDTRC20.java | 11 +++++ .../META-INF/services/haveno.asset.Asset | 2 + .../asset/coins/TetherUSDERC20Test.java | 43 +++++++++++++++++++ .../asset/coins/TetherUSDTRC20Test.java | 42 ++++++++++++++++++ .../java/haveno/core/locale/CurrencyUtil.java | 18 ++++++++ .../core/provider/price/PriceFeedService.java | 7 +-- .../core/provider/price/PriceProvider.java | 2 + .../main/offer/MutableOfferViewModel.java | 2 +- .../presentation/MarketPricePresentation.java | 16 +++++-- 11 files changed, 176 insertions(+), 7 deletions(-) create mode 100644 assets/src/main/java/haveno/asset/Trc20Token.java create mode 100644 assets/src/main/java/haveno/asset/tokens/TetherUSDERC20.java create mode 100644 assets/src/main/java/haveno/asset/tokens/TetherUSDTRC20.java create mode 100644 assets/src/test/java/haveno/asset/coins/TetherUSDERC20Test.java create mode 100644 assets/src/test/java/haveno/asset/coins/TetherUSDTRC20Test.java diff --git a/assets/src/main/java/haveno/asset/Trc20Token.java b/assets/src/main/java/haveno/asset/Trc20Token.java new file mode 100644 index 00000000..3cffa344 --- /dev/null +++ b/assets/src/main/java/haveno/asset/Trc20Token.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Abstract base class for Tron-based {@link Token}s that implement the + * TRC-20 Token Standard. + */ +public abstract class Trc20Token extends Token { + + public Trc20Token(String name, String tickerSymbol) { + super(name, tickerSymbol, new RegexAddressValidator("T[A-Za-z1-9]{33}")); + } +} diff --git a/assets/src/main/java/haveno/asset/tokens/TetherUSDERC20.java b/assets/src/main/java/haveno/asset/tokens/TetherUSDERC20.java new file mode 100644 index 00000000..1afb7ff1 --- /dev/null +++ b/assets/src/main/java/haveno/asset/tokens/TetherUSDERC20.java @@ -0,0 +1,11 @@ +package haveno.asset.tokens; + +import haveno.asset.Erc20Token; + +public class TetherUSDERC20 extends Erc20Token { + public TetherUSDERC20() { + // If you add a new USDT variant or want to change this ticker symbol you should also look here: + // core/src/main/java/haveno/core/provider/price/PriceProvider.java:getAll() + super("Tether USD (ERC20)", "USDT-ERC20"); + } +} diff --git a/assets/src/main/java/haveno/asset/tokens/TetherUSDTRC20.java b/assets/src/main/java/haveno/asset/tokens/TetherUSDTRC20.java new file mode 100644 index 00000000..c5669d12 --- /dev/null +++ b/assets/src/main/java/haveno/asset/tokens/TetherUSDTRC20.java @@ -0,0 +1,11 @@ +package haveno.asset.tokens; + +import haveno.asset.Trc20Token; + +public class TetherUSDTRC20 extends Trc20Token { + public TetherUSDTRC20() { + // If you add a new USDT variant or want to change this ticker symbol you should also look here: + // core/src/main/java/haveno/core/provider/price/PriceProvider.java:getAll() + super("Tether USD (TRC20)", "USDT-TRC20"); + } +} 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 7108c288..350f6f15 100644 --- a/assets/src/main/resources/META-INF/services/haveno.asset.Asset +++ b/assets/src/main/resources/META-INF/services/haveno.asset.Asset @@ -7,3 +7,5 @@ haveno.asset.coins.BitcoinCash haveno.asset.coins.Ether haveno.asset.coins.Litecoin haveno.asset.coins.Monero +haveno.asset.tokens.TetherUSDERC20 +haveno.asset.tokens.TetherUSDTRC20 \ No newline at end of file diff --git a/assets/src/test/java/haveno/asset/coins/TetherUSDERC20Test.java b/assets/src/test/java/haveno/asset/coins/TetherUSDERC20Test.java new file mode 100644 index 00000000..fd7d97ce --- /dev/null +++ b/assets/src/test/java/haveno/asset/coins/TetherUSDERC20Test.java @@ -0,0 +1,43 @@ +/* + * 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 haveno.asset.coins; + +import haveno.asset.AbstractAssetTest; +import haveno.asset.tokens.TetherUSDERC20; + +import org.junit.jupiter.api.Test; + + public class TetherUSDERC20Test extends AbstractAssetTest { + + public TetherUSDERC20Test() { + super(new TetherUSDERC20()); + } + + @Test + public void testValidAddresses() { + assertValidAddress("0x2a65Aca4D5fC5B5C859090a6c34d164135398226"); + assertValidAddress("2a65Aca4D5fC5B5C859090a6c34d164135398226"); + } + + @Test + public void testInvalidAddresses() { + assertInvalidAddress("0x2a65Aca4D5fC5B5C859090a6c34d1641353982266"); + assertInvalidAddress("0x2a65Aca4D5fC5B5C859090a6c34d16413539822g"); + assertInvalidAddress("2a65Aca4D5fC5B5C859090a6c34d16413539822g"); + } + } \ No newline at end of file diff --git a/assets/src/test/java/haveno/asset/coins/TetherUSDTRC20Test.java b/assets/src/test/java/haveno/asset/coins/TetherUSDTRC20Test.java new file mode 100644 index 00000000..7fef554c --- /dev/null +++ b/assets/src/test/java/haveno/asset/coins/TetherUSDTRC20Test.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 haveno.asset.tokens.TetherUSDTRC20; + +import org.junit.jupiter.api.Test; + + public class TetherUSDTRC20Test extends AbstractAssetTest { + + public TetherUSDTRC20Test() { + super(new TetherUSDTRC20()); + } + + @Test + public void testValidAddresses() { + assertValidAddress("TVnmu3E6DYVL4bpAoZnPNEPVUrgC7eSWaX"); + } + + @Test + public void testInvalidAddresses() { + assertInvalidAddress("0x2a65Aca4D5fC5B5C859090a6c34d1641353982266"); + assertInvalidAddress("0x2a65Aca4D5fC5B5C859090a6c34d16413539822g"); + assertInvalidAddress("2a65Aca4D5fC5B5C859090a6c34d16413539822g"); + } + } \ No newline at end of file diff --git a/core/src/main/java/haveno/core/locale/CurrencyUtil.java b/core/src/main/java/haveno/core/locale/CurrencyUtil.java index 45371225..5775d61d 100644 --- a/core/src/main/java/haveno/core/locale/CurrencyUtil.java +++ b/core/src/main/java/haveno/core/locale/CurrencyUtil.java @@ -200,6 +200,8 @@ public class CurrencyUtil { result.add(new CryptoCurrency("BCH", "Bitcoin Cash")); result.add(new CryptoCurrency("ETH", "Ether")); result.add(new CryptoCurrency("LTC", "Litecoin")); + result.add(new CryptoCurrency("USDT-ERC20", "Tether USD (ERC20)")); + result.add(new CryptoCurrency("USDT-TRC20", "Tether USD (TRC20)")); result.sort(TradeCurrency::compareTo); return result; } @@ -295,6 +297,9 @@ public class CurrencyUtil { if (currencyCode != null && isCryptoCurrencyMap.containsKey(currencyCode.toUpperCase())) { return isCryptoCurrencyMap.get(currencyCode.toUpperCase()); } + if (isCryptoCurrencyBase(currencyCode)) { + return true; + } boolean isCryptoCurrency; if (currencyCode == null) { @@ -321,6 +326,19 @@ public class CurrencyUtil { return isCryptoCurrency; } + private static boolean isCryptoCurrencyBase(String currencyCode) { + if (currencyCode == null) return false; + currencyCode = currencyCode.toUpperCase(); + return currencyCode.equals("USDT"); + } + + public static String getCurrencyCodeBase(String currencyCode) { + if (currencyCode == null) return null; + currencyCode = currencyCode.toUpperCase(); + if (currencyCode.contains("USDT")) return "USDT"; + return currencyCode; + } + public static Optional getCryptoCurrency(String currencyCode) { return Optional.ofNullable(cryptoCurrencyMapSupplier.get().get(currencyCode)); } diff --git a/core/src/main/java/haveno/core/provider/price/PriceFeedService.java b/core/src/main/java/haveno/core/provider/price/PriceFeedService.java index 6f3566d1..c5827779 100644 --- a/core/src/main/java/haveno/core/provider/price/PriceFeedService.java +++ b/core/src/main/java/haveno/core/provider/price/PriceFeedService.java @@ -292,15 +292,16 @@ public class PriceFeedService { @Nullable public MarketPrice getMarketPrice(String currencyCode) { synchronized (cache) { - return cache.getOrDefault(currencyCode, null); + return cache.getOrDefault(CurrencyUtil.getCurrencyCodeBase(currencyCode), null); } } private void setHavenoMarketPrice(String currencyCode, Price price) { UserThread.execute(() -> { + String currencyCodeBase = CurrencyUtil.getCurrencyCodeBase(currencyCode); synchronized (cache) { - if (!cache.containsKey(currencyCode) || !cache.get(currencyCode).isExternallyProvidedPrice()) { - cache.put(currencyCode, new MarketPrice(currencyCode, + if (!cache.containsKey(currencyCodeBase) || !cache.get(currencyCodeBase).isExternallyProvidedPrice()) { + cache.put(currencyCodeBase, new MarketPrice(currencyCodeBase, MathUtils.scaleDownByPowerOf10(price.getValue(), CurrencyUtil.isCryptoCurrency(currencyCode) ? CryptoMoney.SMALLEST_UNIT_EXPONENT : TraditionalMoney.SMALLEST_UNIT_EXPONENT), 0, false)); diff --git a/core/src/main/java/haveno/core/provider/price/PriceProvider.java b/core/src/main/java/haveno/core/provider/price/PriceProvider.java index 17bef33a..93162947 100644 --- a/core/src/main/java/haveno/core/provider/price/PriceProvider.java +++ b/core/src/main/java/haveno/core/provider/price/PriceProvider.java @@ -21,6 +21,7 @@ import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import haveno.common.app.Version; import haveno.common.util.MathUtils; +import haveno.core.locale.CurrencyUtil; import haveno.core.provider.HttpClientProvider; import haveno.network.http.HttpClient; import haveno.network.p2p.P2PService; @@ -63,6 +64,7 @@ public class PriceProvider extends HttpClientProvider { String baseCurrencyCode = (String) treeMap.get("baseCurrencyCode"); String counterCurrencyCode = (String) treeMap.get("counterCurrencyCode"); String currencyCode = baseCurrencyCode.equals("XMR") ? counterCurrencyCode : baseCurrencyCode; + currencyCode = CurrencyUtil.getCurrencyCodeBase(currencyCode); double price = (Double) treeMap.get("price"); // json uses double for our timestampSec long value... long timestampSec = MathUtils.doubleToLong((Double) treeMap.get("timestampSec")); diff --git a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java index 33233083..66ca6475 100644 --- a/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java +++ b/desktop/src/main/java/haveno/desktop/main/offer/MutableOfferViewModel.java @@ -268,7 +268,7 @@ public abstract class MutableOfferViewModel ext dataModel.getTradeCurrencyCode())); } volumePromptLabel.bind(createStringBinding( - () -> Res.get("createOffer.volume.prompt", dataModel.getTradeCurrencyCode().get()), + () -> Res.get("createOffer.volume.prompt", CurrencyUtil.getCurrencyCodeBase(dataModel.getTradeCurrencyCode().get())), dataModel.getTradeCurrencyCode())); totalToPay.bind(createStringBinding(() -> HavenoUtils.formatXmr(dataModel.totalToPayAsProperty().get(), true), diff --git a/desktop/src/main/java/haveno/desktop/main/presentation/MarketPricePresentation.java b/desktop/src/main/java/haveno/desktop/main/presentation/MarketPricePresentation.java index ab77b5e0..35293692 100644 --- a/desktop/src/main/java/haveno/desktop/main/presentation/MarketPricePresentation.java +++ b/desktop/src/main/java/haveno/desktop/main/presentation/MarketPricePresentation.java @@ -110,9 +110,19 @@ public class MarketPricePresentation { } private void fillPriceFeedComboBoxItems() { - List currencyItems = preferences.getTradeCurrenciesAsObservable() + + // collect unique currency code bases + List uniqueCurrencyCodeBases = preferences.getTradeCurrenciesAsObservable() .stream() - .map(tradeCurrency -> new PriceFeedComboBoxItem(tradeCurrency.getCode())) + .map(TradeCurrency::getCode) + .map(CurrencyUtil::getCurrencyCodeBase) + .distinct() + .collect(Collectors.toList()); + + // create price feed items + List currencyItems = uniqueCurrencyCodeBases + .stream() + .map(currencyCodeBase -> new PriceFeedComboBoxItem(currencyCodeBase)) .collect(Collectors.toList()); priceFeedComboBoxItems.setAll(currencyItems); } @@ -171,7 +181,7 @@ public class MarketPricePresentation { private Optional findPriceFeedComboBoxItem(String currencyCode) { return priceFeedComboBoxItems.stream() - .filter(item -> item.currencyCode.equals(currencyCode)) + .filter(item -> CurrencyUtil.getCurrencyCodeBase(item.currencyCode).equals(CurrencyUtil.getCurrencyCodeBase(currencyCode))) .findAny(); }