From 35e54b31cb9c5689f65e6c49dec8cd7d18b4bf6e Mon Sep 17 00:00:00 2001 From: b00lean Date: Wed, 29 Oct 2014 11:56:50 +0100 Subject: [PATCH] Added support for Coinkite.com Wallet --- .../res/batm-extensions.xml | 7 + .../extra/bitcoin/BitcoinExtension.java | 9 + .../wallets/coinkite/AccountResponse.java | 92 ++++++++ .../wallets/coinkite/CoinkiteSendRequest.java | 78 +++++++ .../coinkite/CoinkiteSendResponse.java | 171 ++++++++++++++ .../wallets/coinkite/CoinkiteWallet.java | 214 ++++++++++++++++++ .../wallets/coinkite/ICoinkiteAPI.java | 44 ++++ .../extra/litecoin/LitecoinExtension.java | 9 + 8 files changed, 624 insertions(+) create mode 100644 server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/AccountResponse.java create mode 100644 server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendRequest.java create mode 100644 server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendResponse.java create mode 100644 server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteWallet.java create mode 100644 server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/ICoinkiteAPI.java diff --git a/server_extensions_extra/res/batm-extensions.xml b/server_extensions_extra/res/batm-extensions.xml index 0bf853f..0037f31 100644 --- a/server_extensions_extra/res/batm-extensions.xml +++ b/server_extensions_extra/res/batm-extensions.xml @@ -10,6 +10,13 @@ BTC + + + + + BTC + LTC + BTC diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/BitcoinExtension.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/BitcoinExtension.java index 7c53941..c575856 100644 --- a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/BitcoinExtension.java +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/BitcoinExtension.java @@ -22,6 +22,7 @@ import com.generalbytes.batm.server.extensions.extra.bitcoin.paymentprocessors.b import com.generalbytes.batm.server.extensions.extra.bitcoin.sources.BitcoinAverageRateSource; import com.generalbytes.batm.server.extensions.extra.bitcoin.sources.FixPriceRateSource; import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.bitcoind.BATMBitcoindRPCWallet; +import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinkite.CoinkiteWallet; import java.math.BigDecimal; import java.util.*; @@ -76,6 +77,14 @@ public class BitcoinExtension implements IExtension{ String rpcURL = protocol +"://" + username +":" + password + "@" + hostname +":" + port; return new BATMBitcoindRPCWallet(rpcURL,accountName); } + }else if ("coinkite".equalsIgnoreCase(walletType)) { + String apikey = st.nextToken(); + String apiSecret = st.nextToken(); + String accountNumber ="0"; + if (st.hasMoreTokens()) { + accountNumber = st.nextToken(); + } + return new CoinkiteWallet(apikey,apiSecret,accountNumber); } } return null; diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/AccountResponse.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/AccountResponse.java new file mode 100644 index 0000000..8ddeda6 --- /dev/null +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/AccountResponse.java @@ -0,0 +1,92 @@ +/************************************************************************************* + * Copyright (C) 2014 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.bitcoin.wallets.coinkite; + +public class AccountResponse { + private Account account; + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + + public class Account { + private String CK_acct_type; + private String CK_refnum; + private String CK_type; + private String coin_type; + private String created_at; + private String name; + private String quick_deposit; + private Balance balance; + + public String getCK_acct_type() { + return CK_acct_type; + } + + public String getCK_refnum() { + return CK_refnum; + } + + public String getCK_type() { + return CK_type; + } + + public String getCoin_type() { + return coin_type; + } + + public String getCreated_at() { + return created_at; + } + + public String getName() { + return name; + } + + public String getQuick_deposit() { + return quick_deposit; + } + + public Balance getBalance() { + return balance; + } + + public class Balance { + private String currency; + private String string; + + public String getCurrency() { + return currency; + } + + public String getString() { + return string; + } + } + + + + } + + +} diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendRequest.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendRequest.java new file mode 100644 index 0000000..bc18d98 --- /dev/null +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendRequest.java @@ -0,0 +1,78 @@ +/************************************************************************************* + * Copyright (C) 2014 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.bitcoin.wallets.coinkite; + + +public class CoinkiteSendRequest { + private String memo; + private String amount; + private String account; + private String dest; + private String public_msg; + + public CoinkiteSendRequest() { + } + + public CoinkiteSendRequest(String memo, String amount, String accountId, String dest, String public_msg) { + this.memo = memo; + this.amount = amount; + this.account = accountId; + this.dest = dest; + this.public_msg = public_msg; + } + + public String getMemo() { + return memo; + } + + public void setMemo(String memo) { + this.memo = memo; + } + + public String getAmount() { + return amount; + } + + public void setAmount(String amount) { + this.amount = amount; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getDest() { + return dest; + } + + public void setDest(String dest) { + this.dest = dest; + } + + public String getPublic_msg() { + return public_msg; + } + + public void setPublic_msg(String public_msg) { + this.public_msg = public_msg; + } +} diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendResponse.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendResponse.java new file mode 100644 index 0000000..a95d7aa --- /dev/null +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteSendResponse.java @@ -0,0 +1,171 @@ +/************************************************************************************* + * Copyright (C) 2014 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.bitcoin.wallets.coinkite; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CoinkiteSendResponse { + private SendArguments args; + private String next_step; + private Result result; + + + + public String getNext_step() { + return next_step; + } + + public void setNext_step(String next_step) { + this.next_step = next_step; + } + + public Result getResult() { + return result; + } + + public void setResult(Result result) { + this.result = result; + } + + public SendArguments getArgs() { + return args; + } + + public void setArgs(SendArguments args) { + this.args = args; + } + + public class SendArguments { + + } + + public class Result { + @JsonProperty("CK_refnum") + private String CK_refnum; + @JsonProperty("CK_req_type") + private String CK_req_type; + private String account; + private String coin_type; + private String created_at; + private String desc; + private String destination; + private String detail_page; + private boolean include_pin; + private boolean is_completed; + private String memo; + private String send_authcode; + + public String getCK_refnum() { + return CK_refnum; + } + + public void setCK_refnum(String CK_refnum) { + this.CK_refnum = CK_refnum; + } + + public String getCK_req_type() { + return CK_req_type; + } + + public void setCK_req_type(String CK_req_type) { + this.CK_req_type = CK_req_type; + } + + public String getAccount() { + return account; + } + + public void setAccount(String account) { + this.account = account; + } + + public String getCoin_type() { + return coin_type; + } + + public void setCoin_type(String coin_type) { + this.coin_type = coin_type; + } + + public String getCreated_at() { + return created_at; + } + + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getDetail_page() { + return detail_page; + } + + public void setDetail_page(String detail_page) { + this.detail_page = detail_page; + } + + public boolean isInclude_pin() { + return include_pin; + } + + public void setInclude_pin(boolean include_pin) { + this.include_pin = include_pin; + } + + public boolean isIs_completed() { + return is_completed; + } + + public void setIs_completed(boolean is_completed) { + this.is_completed = is_completed; + } + + public String getMemo() { + return memo; + } + + public void setMemo(String memo) { + this.memo = memo; + } + + public String getSend_authcode() { + return send_authcode; + } + + public void setSend_authcode(String send_authcode) { + this.send_authcode = send_authcode; + } + } + + +} diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteWallet.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteWallet.java new file mode 100644 index 0000000..2099332 --- /dev/null +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/CoinkiteWallet.java @@ -0,0 +1,214 @@ +/************************************************************************************* + * Copyright (C) 2014 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.bitcoin.wallets.coinkite; + +import com.generalbytes.batm.server.extensions.ICurrencies; +import com.generalbytes.batm.server.extensions.IWallet; +import si.mazi.rescu.RestProxyFactory; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigDecimal; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.TimeZone; + +public class CoinkiteWallet implements IWallet{ + private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS"); + public static final int CALL_PERIOD_MINIMUM = 2100; //Coinkite cannot be called more often than once in 2 seconds + + private String apiKey; + private String apiSecret; + private String accountId; + + private ICoinkiteAPI api; + public static volatile long lastCall = -1; + + static { + TimeZone tz = TimeZone.getTimeZone("UTC"); + sdf.setTimeZone(tz); + } + + public CoinkiteWallet(String apiKey, String apiSecret, String accountId) { + this.apiKey = apiKey; + this.apiSecret = apiSecret; + if (accountId == null) { + accountId = "0"; + } + + accountId = accountId.replace("#",""); + accountId = Integer.parseInt(accountId) +""; + this.accountId = accountId; + api =RestProxyFactory.createProxy(ICoinkiteAPI.class, "https://api.coinkite.com"); + } + + @Override + public Set getCryptoCurrencies() { + Set result = new HashSet(); + result.add(ICurrencies.BTC); + result.add(ICurrencies.LTC); + return result; + } + + @Override + public String getPreferredCryptoCurrency() { + return ICurrencies.BTC; + } + + @Override + public String getCryptoAddress(String cryptoCurrency) { + if (!(ICurrencies.LTC.equalsIgnoreCase(cryptoCurrency) || ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency))) { + return null; + } + waitForPossibleCall(); + String timestamp = getTimestamp(); + String sign = sign("/v1/account/"+accountId, timestamp, apiSecret); + AccountResponse accountResponse = api.getAccount(apiKey, sign, timestamp, accountId); + lastCall = System.currentTimeMillis(); + if (accountResponse != null && accountResponse.getAccount() != null) { + if ( accountResponse.getAccount().getQuick_deposit() !=null && accountResponse.getAccount().getCoin_type().equalsIgnoreCase(cryptoCurrency)) { + return accountResponse.getAccount().getQuick_deposit(); + } + } + + return null; + } + + @Override + public BigDecimal getCryptoBalance(String cryptoCurrency) { + if (!(ICurrencies.LTC.equalsIgnoreCase(cryptoCurrency) || ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency))) { + return null; + } + waitForPossibleCall(); + String timestamp = getTimestamp(); + String sign = sign("/v1/account/"+accountId, timestamp, apiSecret); + AccountResponse accountResponse = api.getAccount(apiKey, sign, timestamp, accountId); + lastCall = System.currentTimeMillis(); + if (accountResponse != null && accountResponse.getAccount() != null) { + if ( accountResponse.getAccount().getQuick_deposit() !=null && accountResponse.getAccount().getCoin_type().equalsIgnoreCase(cryptoCurrency)) { + if (accountResponse.getAccount().getBalance() != null && accountResponse.getAccount().getBalance().getCurrency().equalsIgnoreCase(cryptoCurrency)) { + return new BigDecimal(accountResponse.getAccount().getBalance().getString()); + } + } + } + return null; + } + + + + @Override + public String sendCoins(String destinationAddress, BigDecimal amount, String cryptoCurrency, String description) { + if (!(ICurrencies.LTC.equalsIgnoreCase(cryptoCurrency) || ICurrencies.BTC.equalsIgnoreCase(cryptoCurrency))) { + return null; + } + + waitForPossibleCall(); + String timestamp = getTimestamp(); + String sign = sign("/v1/new/send", timestamp, apiSecret); + CoinkiteSendResponse response = api.send(apiKey, sign, timestamp, new CoinkiteSendRequest(description, amount.toPlainString(), accountId, destinationAddress, description)); + lastCall = System.currentTimeMillis(); + if (response != null) { + if (response.getResult() != null) { + String ck_refnum = response.getResult().getCK_refnum(); + String send_authcode = response.getResult().getSend_authcode(); + +// System.out.println("ckref = " + ck_refnum); +// System.out.println("authcode = " + send_authcode); + + timestamp = getTimestamp(); + sign = sign("/v1/update/"+ ck_refnum +"/auth_send", timestamp, apiSecret); + waitForPossibleCall(); + response = api.authSend(apiKey, sign, timestamp, ck_refnum, send_authcode); + lastCall = System.currentTimeMillis(); + if (response != null ) { + if (response.getResult() != null) { +// System.out.println("ckref = " + ck_refnum); + return response.getResult().getCK_refnum(); + } + } + + + + } + } + + return null; + } + + private void waitForPossibleCall() { + long now = System.currentTimeMillis(); + if (lastCall != -1) { + long diff = now - lastCall; +// System.out.println("diff = " + diff); + if (diff < CALL_PERIOD_MINIMUM) { + try { + long sleeping = CALL_PERIOD_MINIMUM - diff; +// System.out.println("sleeping = " + sleeping); + Thread.sleep(sleeping); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + lastCall = now; + return; + } + + + private static String sign(String data, String timestamp, String secret) { + if (timestamp == null) { + timestamp = getTimestamp(); + } + try { + Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); + SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256"); + sha256_HMAC.init(secret_key); + + byte[] result = sha256_HMAC.doFinal((data + "|" + timestamp).getBytes()); + return bytesToHexString(result); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (InvalidKeyException e) { + e.printStackTrace(); + } + return null; + } + + private static String getTimestamp() { + return sdf.format(new Date()); + } + + public static String bytesToHexString(byte[] bytes) { + // http://stackoverflow.com/questions/332079 + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < bytes.length; i++) { + String hex = Integer.toHexString(0xFF & bytes[i]); + if (hex.length() == 1) { + sb.append('0'); + } + sb.append(hex); + } + return sb.toString(); + } + +} diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/ICoinkiteAPI.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/ICoinkiteAPI.java new file mode 100644 index 0000000..730363a --- /dev/null +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/bitcoin/wallets/coinkite/ICoinkiteAPI.java @@ -0,0 +1,44 @@ +/************************************************************************************* + * Copyright (C) 2014 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.bitcoin.wallets.coinkite; + + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; + +@Path("/v1") +@Produces(MediaType.APPLICATION_JSON) +public interface ICoinkiteAPI { + @GET + @Path("/account/{accountId}") + AccountResponse getAccount(@HeaderParam("X-CK-Key") String apiKey, @HeaderParam("X-CK-Sign") String signature, @HeaderParam("X-CK-Timestamp") String timestamp, @PathParam("accountId") String accountId); + + @GET + @Path("/my/self") + void getInfo(@HeaderParam("X-CK-Key") String apiKey, @HeaderParam("X-CK-Sign") String signature, @HeaderParam("X-CK-Timestamp") String timestamp); + + @PUT + @Path("/new/send") + @Consumes(MediaType.APPLICATION_JSON) + CoinkiteSendResponse send(@HeaderParam("X-CK-Key") String apiKey, @HeaderParam("X-CK-Sign") String signature, @HeaderParam("X-CK-Timestamp") String timestamp, CoinkiteSendRequest request); + + @PUT + @Path("/update/{refnum}/auth_send") + CoinkiteSendResponse authSend(@HeaderParam("X-CK-Key") String apiKey, @HeaderParam("X-CK-Sign") String signature, @HeaderParam("X-CK-Timestamp") String timestamp, @PathParam("refnum") String refnum, @QueryParam("authcode") String authcode); + +} diff --git a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/litecoin/LitecoinExtension.java b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/litecoin/LitecoinExtension.java index 3b9941d..cb66f53 100644 --- a/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/litecoin/LitecoinExtension.java +++ b/server_extensions_extra/src/com/generalbytes/batm/server/extensions/extra/litecoin/LitecoinExtension.java @@ -18,6 +18,7 @@ package com.generalbytes.batm.server.extensions.extra.litecoin; import com.generalbytes.batm.server.extensions.*; +import com.generalbytes.batm.server.extensions.extra.bitcoin.wallets.coinkite.CoinkiteWallet; import com.generalbytes.batm.server.extensions.extra.litecoin.sources.FixPriceRateSource; import com.generalbytes.batm.server.extensions.extra.litecoin.sources.btce.BTCeRateSource; import com.generalbytes.batm.server.extensions.extra.litecoin.wallets.litecoind.LitecoindRPCWallet; @@ -60,6 +61,14 @@ public class LitecoinExtension implements IExtension{ String rpcURL = protocol +"://" + username +":" + password + "@" + hostname +":" + port; return new LitecoindRPCWallet(rpcURL,accountName); } + }else if ("coinkite".equalsIgnoreCase(walletType)) { + String apikey = st.nextToken(); + String apiSecret = st.nextToken(); + String accountNumber ="0"; + if (st.hasMoreTokens()) { + accountNumber = st.nextToken(); + } + return new CoinkiteWallet(apikey,apiSecret,accountNumber); } } return null;