From c098e339719ecd643626a86ce697fa6777bc06eb Mon Sep 17 00:00:00 2001 From: sidhujag Date: Fri, 18 May 2018 22:22:28 -0700 Subject: [PATCH] Add support for Syscoin -CMC API V2 -Lookup IDs from cryptocurrency symbols in CoinmarketcapRateSource -Support for CAD in CMC API --- README.md | 2 +- .../batm/server/extensions/Currencies.java | 1 + .../dash/sources/coinmarketcap/CMCTicker.java | 9 ++ .../CoinmarketcapRateSource.java | 39 +++-- .../coinmarketcap/ICoinmarketcapAPI.java | 10 +- .../syscoin/SyscoinAddressValidator.java | 52 +++++++ .../extra/syscoin/SyscoinExtension.java | 140 ++++++++++++++++++ .../syscoin/sources/FixPriceRateSource.java | 60 ++++++++ .../wallets/syscoind/SyscoinRPCWallet.java | 119 +++++++++++++++ .../src/main/resources/batm-extensions.xml | 17 +++ .../src/main/resources/sys.png | Bin 0 -> 4185 bytes 11 files changed, 427 insertions(+), 22 deletions(-) create mode 100644 server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinAddressValidator.java create mode 100644 server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinExtension.java create mode 100644 server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/sources/FixPriceRateSource.java create mode 100644 server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/wallets/syscoind/SyscoinRPCWallet.java create mode 100644 server_extensions_extra/src/main/resources/sys.png diff --git a/README.md b/README.md index fd098bc..b55dbf1 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Here is the list of functionality that can be extended with extenstions API: Content ======= * **server_extensions_api** - contains extension api that all extensions use to extend BATM Server's functionality. -* **server_extensions_extra** - reference extension implementation that implements BTC, LTC, DGB, DASH, FLASH, DOGE, NLG, ICG, NBT, GRS and MAX coin support functionality. +* **server_extensions_extra** - reference extension implementation that implements BTC, LTC, DGB, DASH, SYS, FLASH, DOGE, NLG, ICG, NBT, GRS and MAX coin support functionality. * **server_extensions_test** - contains tester for testing the extensions without requirement of having a BATM server Note for developers diff --git a/server_extensions_api/src/main/java/com/generalbytes/batm/server/extensions/Currencies.java b/server_extensions_api/src/main/java/com/generalbytes/batm/server/extensions/Currencies.java index cdb9376..ee9072a 100644 --- a/server_extensions_api/src/main/java/com/generalbytes/batm/server/extensions/Currencies.java +++ b/server_extensions_api/src/main/java/com/generalbytes/batm/server/extensions/Currencies.java @@ -45,6 +45,7 @@ public class Currencies { public static final String POT = "POT"; public static final String SMART = "SMART"; public static final String START = "START"; + public static final String SYS = "SYS"; // Syscoin 3 public static final String TKN = "TKN"; public static final String WDC = "WDC"; public static final String XMR = "XMR"; diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CMCTicker.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CMCTicker.java index 92e8c2c..0896e0d 100644 --- a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CMCTicker.java +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CMCTicker.java @@ -14,6 +14,7 @@ public class CMCTicker { private String symbol; private BigDecimal rank; private BigDecimal price_usd; + private BigDecimal price_cad; private BigDecimal price_eur; private BigDecimal price_btc; @@ -76,6 +77,14 @@ public class CMCTicker { this.price_eur = price_eur; } + public BigDecimal getPrice_cad() { + return price_cad; + } + + public void setPrice_cad(BigDecimal price_cad) { + this.price_cad = price_cad; + } + public BigDecimal getPrice_btc() { return price_btc; } diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CoinmarketcapRateSource.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CoinmarketcapRateSource.java index 823df81..ff67016 100644 --- a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CoinmarketcapRateSource.java +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/CoinmarketcapRateSource.java @@ -1,6 +1,5 @@ package com.generalbytes.batm.server.extensions.extra.dash.sources.coinmarketcap; -import com.generalbytes.batm.server.extensions.Currencies; import com.generalbytes.batm.server.extensions.Currencies; import com.generalbytes.batm.server.extensions.IRateSource; @@ -16,7 +15,7 @@ import si.mazi.rescu.RestProxyFactory; public class CoinmarketcapRateSource implements IRateSource { private ICoinmarketcapAPI api; - + private static HashMap coinIDs = new HashMap(); private String preferredFiatCurrency = Currencies.USD; public CoinmarketcapRateSource(String preferedFiatCurrency) { @@ -27,6 +26,19 @@ public class CoinmarketcapRateSource implements IRateSource { if (Currencies.USD.equalsIgnoreCase(preferedFiatCurrency)) { this.preferredFiatCurrency = Currencies.USD; } + if (Currencies.CAD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferredFiatCurrency = Currencies.CAD; + } + coinIDs.put(Currencies.BTC, 1); + coinIDs.put(Currencies.SYS, 541); + coinIDs.put(Currencies.BCH, 1831); + coinIDs.put(Currencies.BTX, 1654); + coinIDs.put(Currencies.LTC, 2); + coinIDs.put(Currencies.ETH, 1027); + coinIDs.put(Currencies.DASH, 131); + coinIDs.put(Currencies.XMR, 328); + coinIDs.put(Currencies.POT, 122); + coinIDs.put(Currencies.FLASH, 1755); } public CoinmarketcapRateSource() { @@ -37,6 +49,7 @@ public class CoinmarketcapRateSource implements IRateSource { public Set getCryptoCurrencies() { Set result = new HashSet(); result.add(Currencies.BTC); + result.add(Currencies.SYS); result.add(Currencies.BCH); result.add(Currencies.BTX); result.add(Currencies.LTC); @@ -53,6 +66,7 @@ public class CoinmarketcapRateSource implements IRateSource { public Set getFiatCurrencies() { Set result = new HashSet(); result.add(Currencies.USD); + result.add(Currencies.CAD); result.add(Currencies.EUR); return result; } @@ -69,28 +83,25 @@ public class CoinmarketcapRateSource implements IRateSource { if (!getFiatCurrencies().contains(fiatCurrency)) { return null; } - CMCTicker[] tickers; - if(Currencies.FLASH.equalsIgnoreCase(cryptoCurrency)){ - tickers = api.getTickers(cryptoCurrency,fiatCurrency); - }else - tickers = api.getTickers(fiatCurrency); + Integer cryptoId = coinIDs.get(cryptoCurrency); + if(cryptoId == null){ + return null; + } + CMCTicker[] tickers = api.getTickers(cryptoId, fiatCurrency); for (int i = 0; i < tickers.length; i++) { CMCTicker ticker = tickers[i]; if (cryptoCurrency.equalsIgnoreCase(ticker.getSymbol())) { if (Currencies.EUR.equalsIgnoreCase(fiatCurrency)) { return ticker.getPrice_eur(); - }else{ + }else if (Currencies.CAD.equalsIgnoreCase(fiatCurrency)) { + return ticker.getPrice_cad(); + } + else{ return ticker.getPrice_usd(); } } } return null; } - -// public static void main(String[] args) { -// CoinmarketcapRateSource rs = new CoinmarketcapRateSource(Currencies.EUR); -// BigDecimal exchangeRateLast = rs.getExchangeRateLast(Currencies.BTC, Currencies.EUR); -// System.out.println("exchangeRateLast = " + exchangeRateLast); -// } } diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/ICoinmarketcapAPI.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/ICoinmarketcapAPI.java index 230136f..303bb42 100644 --- a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/ICoinmarketcapAPI.java +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/dash/sources/coinmarketcap/ICoinmarketcapAPI.java @@ -20,14 +20,10 @@ package com.generalbytes.batm.server.extensions.extra.dash.sources.coinmarketcap import javax.ws.rs.*; import javax.ws.rs.core.MediaType; -@Path("/v1/ticker") +@Path("/v2/ticker") @Produces(MediaType.APPLICATION_JSON) public interface ICoinmarketcapAPI { @GET - @Path("/") - CMCTicker[] getTickers(@QueryParam("convert") String fiatCurrency); - - @GET - @Path("/{cryptoToGet}/?convert={convert}") - CMCTicker[] getTickers(@PathParam("cryptoToGet") String cryptoToGet,@QueryParam("convert") String fiatCurrency); + @Path("/{id}/") + CMCTicker[] getTickers(@PathParam("id") String id, @QueryParam("convert") String fiatCurrency); } diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinAddressValidator.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinAddressValidator.java new file mode 100644 index 0000000..2c214e1 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinAddressValidator.java @@ -0,0 +1,52 @@ +/************************************************************************************* + * Copyright (C) 2014-2016 GENERAL BYTES s.r.o. All rights reserved. + * + * This software may be distributed and modified under the terms of the GNU + * General Public License version 2 (GPL2) as published by the Free Software + * Foundation and appearing in the file GPL2.TXT included in the packaging of + * this file. Please note that GPL2 Section 2[b] requires that all works based + * on this software must also be made publicly available under the terms of + * the GPL2 ("Copyleft"). + * + * Contact information + * ------------------- + * + * GENERAL BYTES s.r.o. + * Web : http://www.generalbytes.com + * + ************************************************************************************/ +package com.generalbytes.batm.server.extensions.extra.dash; + +import com.generalbytes.batm.server.coinutil.AddressFormatException; +import com.generalbytes.batm.server.coinutil.Base58; +import com.generalbytes.batm.server.extensions.ExtensionsUtil; +import com.generalbytes.batm.server.extensions.ICryptoAddressValidator; + +public class SyscoinAddressValidator implements ICryptoAddressValidator { + @Override + public boolean isAddressValid(String address) { + if (address.startsWith("S")) { + try { + Base58.decodeToBigInteger(address); + Base58.decodeChecked(address); + } catch (AddressFormatException e) { + e.printStackTrace(); + return false; + } + return true; + }else{ + return false; + } + } + + @Override + public boolean isPaperWalletSupported() { + return false; + } + + @Override + public boolean mustBeBase58Address() { + return true; + } + +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinExtension.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinExtension.java new file mode 100644 index 0000000..b4a8b55 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/SyscoinExtension.java @@ -0,0 +1,140 @@ +/************************************************************************************* + * Copyright (C) 2014-2016 GENERAL BYTES s.r.o. All rights reserved. + * + * This software may be distributed and modified under the terms of the GNU + * General Public License version 2 (GPL2) as published by the Free Software + * Foundation and appearing in the file GPL2.TXT included in the packaging of + * this file. Please note that GPL2 Section 2[b] requires that all works based + * on this software must also be made publicly available under the terms of + * the GPL2 ("Copyleft"). + * + * Contact information + * ------------------- + * + * GENERAL BYTES s.r.o. + * Web : http://www.generalbytes.com + * + ************************************************************************************/ +package com.generalbytes.batm.server.extensions.extra.syscoin; + +import com.generalbytes.batm.server.extensions.*; +import com.generalbytes.batm.server.extensions.extra.syscoin.sources.FixPriceRateSource; +import com.generalbytes.batm.server.extensions.extra.dash.sources.coinmarketcap.CoinmarketcapRateSource; +import com.generalbytes.batm.server.extensions.extra.syscoin.wallets.syscoind.SyscoinRPCWallet; +import com.generalbytes.batm.server.extensions.watchlist.IWatchList; + +import java.math.BigDecimal; +import java.util.*; + +public class SyscoinExtension implements IExtension{ + @Override + public String getName() { + return "BATM Syscoin extra extension"; + } + + @Override + public IExchange createExchange(String exchangeLogin) { + return null; + } + + @Override + public IPaymentProcessor createPaymentProcessor(String paymentProcessorLogin) { + return null; //no payment processors available + } + + @Override + public IWallet createWallet(String walletLogin) { + if (walletLogin !=null && !walletLogin.trim().isEmpty()) { + StringTokenizer st = new StringTokenizer(walletLogin,":"); + String walletType = st.nextToken(); + + if ("syscoind".equalsIgnoreCase(walletType)) { + //"syscoind:protocol:user:password:ip:port:accountname" + + String protocol = st.nextToken(); + String username = st.nextToken(); + String password = st.nextToken(); + String hostname = st.nextToken(); + String port = st.nextToken(); + String accountName =""; + if (st.hasMoreTokens()) { + accountName = st.nextToken(); + } + + + if (protocol != null && username != null && password != null && hostname !=null && port != null && accountName != null) { + String rpcURL = protocol +"://" + username +":" + password + "@" + hostname +":" + port; + return new SyscoinRPCWallet(rpcURL,accountName); + } + } + + } + return null; + } + + @Override + public ICryptoAddressValidator createAddressValidator(String cryptoCurrency) { + if (Currencies.SYS.equalsIgnoreCase(cryptoCurrency)) { + return new SyscoinAddressValidator(); + } + return null; + } + + @Override + public IPaperWalletGenerator createPaperWalletGenerator(String cryptoCurrency) { + return null; + } + + @Override + public IRateSource createRateSource(String sourceLogin) { + if (sourceLogin != null && !sourceLogin.trim().isEmpty()) { + StringTokenizer st = new StringTokenizer(sourceLogin, ":"); + String exchangeType = st.nextToken(); + if ("syscoinfix".equalsIgnoreCase(exchangeType)) { + BigDecimal rate = BigDecimal.ZERO; + if (st.hasMoreTokens()) { + try { + rate = new BigDecimal(st.nextToken()); + } catch (Throwable e) { + } + } + String preferedFiatCurrency = Currencies.USD; + if (st.hasMoreTokens()) { + preferedFiatCurrency = st.nextToken().toUpperCase(); + } + return new FixPriceRateSource(rate, preferedFiatCurrency); + } else if ("coinmarketcap".equalsIgnoreCase(exchangeType)) { + String preferedFiatCurrency = Currencies.USD; + if (st.hasMoreTokens()) { + preferedFiatCurrency = st.nextToken().toUpperCase(); + } + return new CoinmarketcapRateSource(preferedFiatCurrency); + } + } + return null; + } + + @Override + public Set getSupportedCryptoCurrencies() { + Set result = new HashSet(); + result.add(Currencies.BTC); + result.add(Currencies.SYS); + result.add(Currencies.BTX); + result.add(Currencies.BCH); + result.add(Currencies.LTC); + result.add(Currencies.XMR); + result.add(Currencies.DASH); + result.add(Currencies.POT); + return result; + } + + @Override + public Set getSupportedWatchListsNames() { + return null; + } + + @Override + public IWatchList getWatchList(String name) { + return null; + } +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/sources/FixPriceRateSource.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/sources/FixPriceRateSource.java new file mode 100644 index 0000000..dbe56b6 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/sources/FixPriceRateSource.java @@ -0,0 +1,60 @@ +package com.generalbytes.batm.server.extensions.extra.syscoin.sources; + +import com.generalbytes.batm.server.extensions.Currencies; +import com.generalbytes.batm.server.extensions.Currencies; +import com.generalbytes.batm.server.extensions.IRateSource; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; + +/** + * Created by b00lean on 7/31/14. + */ +public class FixPriceRateSource implements IRateSource { + private BigDecimal rate = BigDecimal.ZERO; + + private String preferedFiatCurrency = Currencies.USD; + + public FixPriceRateSource(BigDecimal rate,String preferedFiatCurrency) { + this.rate = rate; + if (Currencies.EUR.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = Currencies.EUR; + } + if (Currencies.USD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = Currencies.USD; + } + if (Currencies.CAD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = Currencies.CAD; + } + } + + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(Currencies.SYS); + return result; + } + + @Override + public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) { + if (Currencies.SYS.equalsIgnoreCase(cryptoCurrency)) { + return rate; + } + return null; + } + + @Override + public Set getFiatCurrencies() { + Set result = new HashSet(); + result.add(Currencies.USD); + result.add(Currencies.CAD); + result.add(Currencies.EUR); + return result; + } + @Override + public String getPreferredFiatCurrency() { + return preferedFiatCurrency; + } + +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/wallets/syscoind/SyscoinRPCWallet.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/wallets/syscoind/SyscoinRPCWallet.java new file mode 100644 index 0000000..7d7c75d --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/syscoin/wallets/syscoind/SyscoinRPCWallet.java @@ -0,0 +1,119 @@ +/************************************************************************************* + * Copyright (C) 2014-2016 GENERAL BYTES s.r.o. All rights reserved. + * + * This software may be distributed and modified under the terms of the GNU + * General Public License version 2 (GPL2) as published by the Free Software + * Foundation and appearing in the file GPL2.TXT included in the packaging of + * this file. Please note that GPL2 Section 2[b] requires that all works based + * on this software must also be made publicly available under the terms of + * the GPL2 ("Copyleft"). + * + * Contact information + * ------------------- + * + * GENERAL BYTES s.r.o. + * Web : http://www.generalbytes.com + * + ************************************************************************************/ +package com.generalbytes.batm.server.extensions.extra.syscoin.wallets.syscoind; + +import com.azazar.bitcoin.jsonrpcclient.BitcoinException; +import com.azazar.bitcoin.jsonrpcclient.BitcoinJSONRPCClient; +import com.generalbytes.batm.server.extensions.Currencies; +import com.generalbytes.batm.server.extensions.IWallet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.net.MalformedURLException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SyscoinPCWallet implements IWallet{ + private static final Logger log = LoggerFactory.getLogger(SyscoinPCWallet.class); + private static final String CRYPTO_CURRENCY = Currencies.SYS; + + public SyscoinPCWallet(String rpcURL, String accountName) { + this.rpcURL = rpcURL; + this.accountName = accountName; + } + + private String rpcURL; + private String accountName; + + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(CRYPTO_CURRENCY); + return result; + + } + + @Override + public String getPreferredCryptoCurrency() { + return CRYPTO_CURRENCY; + } + + @Override + public String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency, String description) { + if (!CRYPTO_CURRENCY.equalsIgnoreCase(cryptoCurrency)) { + log.error("Syscoind wallet error: unknown cryptocurrency."); + return null; + } + + log.info("Syscoind sending coins from " + accountName + " to: " + destinationAddress + " " + amount); + try { + String result = getClient(rpcURL).sendFrom(accountName, destinationAddress,amount.doubleValue()); + log.debug("result = " + result); + return result; + } catch (BitcoinException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public String getCryptoAddress(String cryptoCurrency) { + if (!CRYPTO_CURRENCY.equalsIgnoreCase(cryptoCurrency)) { + log.error("Syscoind wallet error: unknown cryptocurrency."); + return null; + } + + try { + List addressesByAccount = getClient(rpcURL).getAddressesByAccount(accountName); + if (addressesByAccount == null || addressesByAccount.size() == 0) { + return null; + }else{ + return addressesByAccount.get(0); + } + } catch (BitcoinException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public BigDecimal getCryptoBalance(String cryptoCurrency) { + if (!CRYPTO_CURRENCY.equalsIgnoreCase(cryptoCurrency)) { + log.error("Syscoind wallet error: unknown cryptocurrency: " + cryptoCurrency); + return null; + } + try { + double balance = getClient(rpcURL).getBalance(accountName); + return BigDecimal.valueOf(balance); + } catch (BitcoinException e) { + e.printStackTrace(); + return null; + } + } + + private BitcoinJSONRPCClient getClient(String rpcURL) { + try { + return new BitcoinJSONRPCClient(rpcURL); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/server_extensions_extra/src/main/resources/batm-extensions.xml b/server_extensions_extra/src/main/resources/batm-extensions.xml index 23ac2c9..9c9e131 100644 --- a/server_extensions_extra/src/main/resources/batm-extensions.xml +++ b/server_extensions_extra/src/main/resources/batm-extensions.xml @@ -57,6 +57,7 @@ BTC + SYS BCH LTC ETH @@ -227,6 +228,22 @@ + + + + + + + + + SYS + + + + SYS + + + diff --git a/server_extensions_extra/src/main/resources/sys.png b/server_extensions_extra/src/main/resources/sys.png new file mode 100644 index 0000000000000000000000000000000000000000..b57b36546f5a2aac76723794af9b80ec9a1d248b GIT binary patch literal 4185 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 literal 0 HcmV?d00001