diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/PotcoinAddressValidator.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/PotcoinAddressValidator.java new file mode 100644 index 0000000..e2e9898 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/PotcoinAddressValidator.java @@ -0,0 +1,35 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin; + +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 PotcoinAddressValidator implements ICryptoAddressValidator { + + @Override + public boolean isAddressValid(String address) { + if (address.startsWith("P")) { + 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/potcoin/PotcoinExtension.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/PotcoinExtension.java new file mode 100644 index 0000000..87c78f6 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/PotcoinExtension.java @@ -0,0 +1,107 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin; + +import com.generalbytes.batm.server.extensions.*; +import com.generalbytes.batm.server.extensions.extra.potcoin.sources.FixPriceRateSource; +import com.generalbytes.batm.server.extensions.extra.potcoin.sources.coinmarketcap.CoinmarketcapRateSource; +import com.generalbytes.batm.server.extensions.extra.potcoin.wallets.potwallet.Potwallet; +import com.generalbytes.batm.server.extensions.watchlist.IWatchList; + +import java.math.BigDecimal; +import java.util.*; + +public class PotcoinExtension implements IExtension{ + @Override + public String getName() { + return "BATM Potcoin extension"; + } + + @Override + public IExchange createExchange(String exchangeLogin) { + return null; + } + + @Override + public IWallet createWallet(String walletLogin) { + if (walletLogin !=null && !walletLogin.trim().isEmpty()) { + StringTokenizer st = new StringTokenizer(walletLogin,":"); + String walletType = st.nextToken(); + + if ("potwallet".equalsIgnoreCase(walletType)) { + String publicKey = st.nextToken(); + String privateKey = st.nextToken(); + String walletId = st.nextToken(); + + if (publicKey != null && privateKey != null && walletId != null) { + return new Potwallet(publicKey,privateKey,walletId); + } + } + } + return null; + } + + @Override + public ICryptoAddressValidator createAddressValidator(String cryptoCurrency) { + if (ICurrencies.POT.equalsIgnoreCase(cryptoCurrency)) { + return new PotcoinAddressValidator(); + } + return null; + } + + @Override + public IPaperWalletGenerator createPaperWalletGenerator(String cryptoCurrency) { + return null; + } + + @Override + public IPaymentProcessor createPaymentProcessor(String paymentProcessorLogin) { + return null; //no payment processors available + } + + @Override + public IRateSource createRateSource(String sourceLogin) { + if (sourceLogin != null && !sourceLogin.trim().isEmpty()) { + StringTokenizer st = new StringTokenizer(sourceLogin,":"); + String rsType = st.nextToken(); + + if ("potfix".equalsIgnoreCase(rsType)) { + BigDecimal rate = BigDecimal.ZERO; + if (st.hasMoreTokens()) { + try { + rate = new BigDecimal(st.nextToken()); + } catch (Throwable e) { + } + } + String preferedFiatCurrency = ICurrencies.CAD; + if (st.hasMoreTokens()) { + preferedFiatCurrency = st.nextToken().toUpperCase(); + } + return new FixPriceRateSource(rate,preferedFiatCurrency); + } else if ("coinmarketcap".equalsIgnoreCase(rsType)) { + String preferredFiatCurrency = ICurrencies.CAD; + if (st.hasMoreTokens()) { + preferredFiatCurrency = st.nextToken(); + } + return new CoinmarketcapRateSource(preferredFiatCurrency); + } + + } + return null; + } + + @Override + public Set getSupportedCryptoCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.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/potcoin/sources/FixPriceRateSource.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/FixPriceRateSource.java new file mode 100644 index 0000000..6ed0301 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/FixPriceRateSource.java @@ -0,0 +1,53 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin.sources; + +import com.generalbytes.batm.server.extensions.ICurrencies; +import com.generalbytes.batm.server.extensions.IRateSource; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; + +public class FixPriceRateSource implements IRateSource { + private BigDecimal rate = BigDecimal.ZERO; + + private String preferedFiatCurrency = ICurrencies.CAD; + + public FixPriceRateSource(BigDecimal rate,String preferedFiatCurrency) { + this.rate = rate; + if (ICurrencies.CAD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = ICurrencies.CAD; + } + if (ICurrencies.USD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = ICurrencies.USD; + } + } + + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.POT); + return result; + } + + @Override + public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) { + if (ICurrencies.POT.equalsIgnoreCase(cryptoCurrency)) { + return rate; + } + return null; + } + + @Override + public Set getFiatCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.USD); + result.add(ICurrencies.CAD); + return result; + } + + @Override + public String getPreferredFiatCurrency() { + return preferedFiatCurrency; + } + +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapRateSource.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapRateSource.java new file mode 100644 index 0000000..1b602bd --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapRateSource.java @@ -0,0 +1,100 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin.sources.coinmarketcap; + +import com.generalbytes.batm.server.extensions.ICurrencies; +import com.generalbytes.batm.server.extensions.IRateSource; +import si.mazi.rescu.RestProxyFactory; + +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; + +public class CoinmarketcapRateSource implements IRateSource { + + private String preferedFiatCurrency = ICurrencies.USD; + private ICoinmarketcapRateAPI api; + + /** + * Constructor + */ + public CoinmarketcapRateSource(String preferedFiatCurrency) { + if (ICurrencies.USD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = ICurrencies.USD; + } + if (ICurrencies.CAD.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = ICurrencies.CAD; + } + if (ICurrencies.EUR.equalsIgnoreCase(preferedFiatCurrency)) { + this.preferedFiatCurrency = ICurrencies.EUR; + } + api = RestProxyFactory.createProxy(ICoinmarketcapRateAPI.class, "https://api.coinmarketcap.com"); + } + + /** + * This method returns list of supported crypto currencies + * @return + */ + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.POT); + //result.add(ICurrencies.BTC); + return result; + } + + /** + * Returns current price of cryptocurrency in specified fiat currency + * @param cryptoCurrency + * @param fiatCurrency + * @return + */ + @Override + public BigDecimal getExchangeRateLast(String cryptoCurrency, String fiatCurrency) { + // coinmarketcap uses coin name instead coin symbol + if (ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency)) { + cryptoCurrency = "bitcoin"; + } else if (ICurrencies.POT.equalsIgnoreCase(cryptoCurrency)) { + cryptoCurrency = "potcoin"; + } + + CoinmarketcapResponse[] response = api.getRequest(cryptoCurrency, fiatCurrency); + + if (response != null) { + if (ICurrencies.USD.equalsIgnoreCase(fiatCurrency)) { + BigDecimal rate = response[0].getPrice_usd(); + return rate; + } else if (ICurrencies.CAD.equalsIgnoreCase(fiatCurrency)) { + BigDecimal rate = response[0].getPrice_cad(); + return rate; + } else if (ICurrencies.EUR.equalsIgnoreCase(fiatCurrency)) { + BigDecimal rate = response[0].getPrice_eur(); + return rate; + } else { + return null; + } + } + return null; + } + + /** + * This method returns list of supported fiat currencies + * @return + */ + @Override + public Set getFiatCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.USD); + result.add(ICurrencies.CAD); + result.add(ICurrencies.EUR); + return result; + } + + /** + * Returns fiat currency that is used for actual purchases of cryptocurrency by server + * @return + */ + @Override + public String getPreferredFiatCurrency() { + return preferedFiatCurrency; + } + +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapResponse.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapResponse.java new file mode 100644 index 0000000..a63dca5 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/CoinmarketcapResponse.java @@ -0,0 +1,34 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin.sources.coinmarketcap; + +import java.math.BigDecimal; + +public class CoinmarketcapResponse { + + private BigDecimal price_usd; + private BigDecimal price_cad; + private BigDecimal price_eur; + + public BigDecimal getPrice_usd() { + return price_usd; + } + + public void setPrice_usd(BigDecimal price_usd) { + this.price_usd = price_usd; + } + + public BigDecimal getPrice_cad() { + return price_cad; + } + + public void setPrice_cad(BigDecimal price_cad) { + this.price_cad = price_cad; + } + + public BigDecimal getPrice_eur() { + return price_eur; + } + + public void setPrice_eur(BigDecimal price_eur) { + this.price_eur = price_eur; + } +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/ICoinmarketcapRateAPI.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/ICoinmarketcapRateAPI.java new file mode 100644 index 0000000..16251e4 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/sources/coinmarketcap/ICoinmarketcapRateAPI.java @@ -0,0 +1,15 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin.sources.coinmarketcap; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/v1") +@Produces(MediaType.APPLICATION_JSON) +public interface ICoinmarketcapRateAPI { + @GET + @Path("/ticker/{cryptocurrency}/?convert={fiatcurrency}") + CoinmarketcapResponse[] getRequest(@PathParam("cryptocurrency") String cryptoCurrency, @PathParam("fiatcurrency") String fiatCurrency); +} diff --git a/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/wallets/potwallet/Potwallet.java b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/wallets/potwallet/Potwallet.java new file mode 100644 index 0000000..ad47524 --- /dev/null +++ b/server_extensions_extra/src/main/java/com/generalbytes/batm/server/extensions/extra/potcoin/wallets/potwallet/Potwallet.java @@ -0,0 +1,168 @@ +package com.generalbytes.batm.server.extensions.extra.potcoin.wallets.potwallet; + +import com.generalbytes.batm.server.extensions.ICurrencies; +import com.generalbytes.batm.server.extensions.IWallet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import si.mazi.rescu.HttpStatusIOException; +import si.mazi.rescu.RestProxyFactory; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import java.util.Random; + +public class Potwallet implements IWallet { + private static final Logger log = LoggerFactory.getLogger("batm.master.Potwallet"); + + private String nonce; + private String accessHash; + + private String publicKey; + private String privateKey; + private String walletId; + + private PotwalletAPI api; + + public Potwallet(String publicKey, String privateKey, String walletId) { + this.nonce = generateRandomString(8); + + this.publicKey = publicKey; + this.privateKey = privateKey; + this.walletId = walletId; + api = RestProxyFactory.createProxy(PotwalletAPI.class, "https://api.potwallet.com"); + } + + @Override + public String getPreferredCryptoCurrency() { + return ICurrencies.POT; + } + + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.POT); + return result; + } + + @Override + public String getCryptoAddress(String cryptoCurrency) { + if (!getCryptoCurrencies().contains(cryptoCurrency)) { + log.error("Cryptocurrency " + cryptoCurrency + " not supported."); + return null; + } + if (walletId != null) { + return walletId; + } + try { + accessHash = generateHash(privateKey, "https://api.potwallet.com/v1/address", nonce); + PotwalletResponse response = api.getAddress(publicKey, accessHash, nonce); + if (response != null && response.getMessage() != null && response.getSuccess()) { + return new String(response.getMessage()); + } + } catch (HttpStatusIOException e) { + log.error(e.getHttpBody()); + } catch (IOException e) { + log.error("", e); + } + return null; + } + + @Override + public BigDecimal getCryptoBalance(String cryptoCurrency) { + if (!getCryptoCurrencies().contains(cryptoCurrency)) { + log.error("Cryptocurrency " + cryptoCurrency + " not supported."); + return null; + } + try { + accessHash = generateHash(privateKey, "https://api.potwallet.com/v1/balance", nonce); + PotwalletResponse response = api.getCryptoBalance(publicKey, accessHash, nonce); + if (response != null && response.getMessage() != null && response.getSuccess()) { + log.debug("Transaction " + response.getMessage() + " sent."); + return new BigDecimal(response.getMessage()); + } + } catch (HttpStatusIOException e) { + log.error(e.getHttpBody()); + } catch (IOException e) { + log.error("", e); + } + return null; + } + + @Override + public String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency, String description) { + if (!getCryptoCurrencies().contains(cryptoCurrency)) { + log.error("Cryptocurrency " + cryptoCurrency + " not supported."); + return null; + } + try{ + accessHash = generateHash(privateKey, "https://api.potwallet.com/v1/send", nonce); + PotwalletResponse response = api.sendPots(publicKey, accessHash, nonce, destinationAddress, amount.stripTrailingZeros()); + if (response != null && response.getMessage() != null && response.getSuccess()) { + return new String(response.getMessage()); + } + } catch (HttpStatusIOException e) { + log.error(e.getHttpBody()); + } catch (IOException e) { + log.error("", e); + } + return null; + } + + private static String generateRandomString(int length) + { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + Random rng = new Random(); + char[] text = new char[length]; + for (int i = 0; i < length; i++) + { + text[i] = characters.charAt(rng.nextInt(characters.length())); + } + return new String(text); + } + + private static String generateHash(String privateHash, String endPoint, String nonce ) + { + String digest = null; + try + { + String content = endPoint + nonce; + + SecretKeySpec key = new SecretKeySpec((privateHash).getBytes("UTF-8"), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(key); + + byte[] bytes = mac.doFinal(content.getBytes("ASCII")); + + StringBuffer hash = new StringBuffer(); + + for (int i=0; i