Skip to content

Commit

Permalink
Refactor project structure
Browse files Browse the repository at this point in the history
  • Loading branch information
echsylon committed Aug 31, 2017
1 parent 5bca489 commit a6e3c9a
Show file tree
Hide file tree
Showing 49 changed files with 433 additions and 309 deletions.
43 changes: 29 additions & 14 deletions library/src/main/java/com/echsylon/kraken/Kraken.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package com.echsylon.kraken;

import com.echsylon.blocks.network.OkHttpNetworkClient;
import com.echsylon.kraken.dto.Depth;
import com.echsylon.kraken.dto.OrderAddReceipt;
import com.echsylon.kraken.dto.Trade;
import com.echsylon.kraken.internal.CallCounter;
import com.echsylon.kraken.request.AccountBalanceRequestBuilder;
import com.echsylon.kraken.request.AddOrderRequestBuilder;
import com.echsylon.kraken.request.AssetInfoRequestBuilder;
import com.echsylon.kraken.request.CancelOrderRequestBuilder;
import com.echsylon.kraken.request.ClosedOrdersRequestBuilder;
import com.echsylon.kraken.request.LedgersRequestBuilder;
import com.echsylon.kraken.request.OhlcDataRequestBuilder;
import com.echsylon.kraken.request.OpenOrdersRequestBuilder;
import com.echsylon.kraken.request.OpenPositionsRequestBuilder;
import com.echsylon.kraken.request.OrderBookRequestBuilder;
import com.echsylon.kraken.request.QueryLedgersRequestBuilder;
import com.echsylon.kraken.request.QueryOrdersRequestBuilder;
import com.echsylon.kraken.request.QueryTradesRequestBuilder;
import com.echsylon.kraken.request.RecentSpreadRequestBuilder;
import com.echsylon.kraken.request.RecentTradesRequestBuilder;
import com.echsylon.kraken.request.ServerTimeRequestBuilder;
import com.echsylon.kraken.request.TickerInfoRequestBuilder;
import com.echsylon.kraken.request.TradableAssetPairsRequestBuilder;
import com.echsylon.kraken.request.TradeBalanceRequestBuilder;
import com.echsylon.kraken.request.TradeHistoryRequestBuilder;
import com.echsylon.kraken.request.TradeVolumeRequestBuilder;

import java.io.File;

import static com.echsylon.kraken.Utils.base64Decode;
import static com.echsylon.kraken.internal.Utils.base64Decode;

/**
* This class describes the Kraken API.
Expand Down Expand Up @@ -64,10 +83,6 @@ public static void clearCallRateLimit() {
*
* @param baseUrl The base url to the test environment.
*/
Kraken(String baseUrl) {
this(baseUrl, null, null);
}

Kraken(String baseUrl, String key, String secret) {
this.baseUrl = baseUrl;
this.key = key;
Expand Down Expand Up @@ -165,7 +180,7 @@ public OhlcDataRequestBuilder getOhlcData(final String pair) {
* @return A request builder object to configure the request and any client
* side cache metrics with, and to attach any callback implementations to.
*/
public RequestBuilder<Dictionary<Depth>> getOrderBook(final String pair) {
public OrderBookRequestBuilder getOrderBook(final String pair) {
return new OrderBookRequestBuilder(callCounter, baseUrl, key, secret)
.useAssetPair(pair);
}
Expand All @@ -178,7 +193,7 @@ public RequestBuilder<Dictionary<Depth>> getOrderBook(final String pair) {
* @return A request builder object to configure the request and any client
* side cache metrics with, and to attach any callback implementations to.
*/
public RequestBuilder<Dictionary<Trade[]>> getRecentTrades(final String pair) {
public RecentTradesRequestBuilder getRecentTrades(final String pair) {
return new RecentTradesRequestBuilder(callCounter, baseUrl, key, secret)
.useAssetPair(pair);
}
Expand Down Expand Up @@ -327,10 +342,10 @@ public TradeVolumeRequestBuilder getTradeVolume() {
* @return A request builder object to configure the request and any client
* side cache metrics with, and to attach any callback implementations to.
*/
public RequestBuilder<OrderAddReceipt> addStandardOrder(final String pair,
final String type,
final String orderType,
final String price) {
public AddOrderRequestBuilder addStandardOrder(final String pair,
final String type,
final String orderType,
final String price) {
return new AddOrderRequestBuilder(callCounter, baseUrl, key, secret)
.useAssetPair(pair)
.useType(type)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.echsylon.kraken;
package com.echsylon.kraken.internal;

/**
* This class know how to calculate the call rate limit state for API requests
* and will temporary block the thread if the limit is exceeded.
*/
final class CallCounter {
public final class CallCounter {
private final int decreaseInterval;
private final int maxCount;

Expand All @@ -18,7 +18,7 @@ final class CallCounter {
*
* @param tier The tier that decides which call rate limit rules to apply.
*/
CallCounter(int tier) {
public CallCounter(int tier) {
lastCallTime = 0L;
callCounter = 0;

Expand Down Expand Up @@ -50,7 +50,7 @@ final class CallCounter {
*
* @param cost The cost of the request being handled right now.
*/
synchronized void allocate(int cost) {
public synchronized void allocate(int cost) {
// Early bail-out
if (lastCallTime == 0L) {
callCounter += cost;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.echsylon.kraken;
package com.echsylon.kraken.internal;

import com.echsylon.kraken.Dictionary;
import com.echsylon.kraken.dto.AssetPair;
import com.echsylon.kraken.dto.Depth;
import com.echsylon.kraken.dto.Ohlc;
Expand All @@ -22,7 +23,7 @@
* are used for those cases where the Kraken API returns an array of data as a
* tuple that's more suitable to represent as a POJO.
*/
final class KrakenTypeAdapterFactory implements TypeAdapterFactory {
public final class KrakenTypeAdapterFactory implements TypeAdapterFactory {

@Override
@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package com.echsylon.kraken;
package com.echsylon.kraken.internal;

import android.net.Uri;
import android.util.Base64;

import com.annimon.stream.Stream;
import com.echsylon.blocks.network.NetworkClient;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
* This class provides internal helper functions.
*/
final class Utils {
public final class Utils {

/**
* Joins any provided non-empty string fragments into a comma separated
Expand All @@ -25,7 +31,7 @@ final class Utils {
* @param fragments The string fragments to join.
* @return A comma separated string or null if no fragments provided.
*/
static String join(String... fragments) {
public static String join(String... fragments) {
if (fragments == null || fragments.length <= 0)
return null;

Expand All @@ -50,7 +56,7 @@ static String join(String... fragments) {
* @param value The byte array to express as a string.
* @return The value as a string or null if the input is null.
*/
static String asString(byte[] value) {
public static String asString(byte[] value) {
return value != null ?
new String(value) :
null;
Expand All @@ -62,7 +68,7 @@ static String asString(byte[] value) {
* @param value The object who's value to express as a string.
* @return The value as a string or null if the input is null.
*/
static String asString(Long value) {
public static String asString(Long value) {
return value != null ?
value.toString() :
null;
Expand All @@ -74,7 +80,7 @@ static String asString(Long value) {
* @param value The object who's value to express as a string.
* @return The value as a string or null if the input is null.
*/
static String asString(Integer value) {
public static String asString(Integer value) {
return value != null ?
value.toString() :
null;
Expand All @@ -86,7 +92,7 @@ static String asString(Integer value) {
* @param value The object who's value to express as a string.
* @return The "true" or "false" or null if the input is null.
*/
static String asString(Boolean value) {
public static String asString(Boolean value) {
return value != null ?
value.toString() :
null;
Expand All @@ -99,7 +105,7 @@ static String asString(Boolean value) {
* @param string The string to convert to a byte array.
* @return The string byte array or null if the input is null.
*/
static byte[] asBytes(String string) {
public static byte[] asBytes(String string) {
if (string == null)
return null;

Expand All @@ -114,7 +120,7 @@ static byte[] asBytes(String string) {
* @return The resulting byte array (may be empty) or null if the input is
* null.
*/
static byte[] concat(byte[]... bytes) {
public static byte[] concat(byte[]... bytes) {
return bytes != null ?
Stream.of(bytes)
.reduce(new ByteArrayOutputStream(), (stream, array) -> {
Expand All @@ -136,7 +142,7 @@ static byte[] concat(byte[]... bytes) {
* @param secret The encryption secret.
* @return the encrypted byte array.
*/
static byte[] hmacSha512(byte[] message, byte[] secret) {
public static byte[] hmacSha512(byte[] message, byte[] secret) {
try {
Mac mac = Mac.getInstance("HmacSHA512");
mac.init(new SecretKeySpec(secret, "HmacSHA512"));
Expand All @@ -152,7 +158,7 @@ static byte[] hmacSha512(byte[] message, byte[] secret) {
* @param message The data to encrypt.
* @return the encrypted byte array.
*/
static byte[] sha256(byte[] message) {
public static byte[] sha256(byte[] message) {
if (message == null)
return null;

Expand All @@ -170,7 +176,7 @@ static byte[] sha256(byte[] message) {
* @param string The string to decode.
* @return The decoded string as a byte array.
*/
static byte[] base64Decode(String string) {
public static byte[] base64Decode(String string) {
return string != null ?
Base64.decode(string, Base64.DEFAULT) :
null;
Expand All @@ -182,10 +188,106 @@ static byte[] base64Decode(String string) {
* @param bytes The bytes to encode.
* @return The encoded byte array as a string.
*/
static String base64Encode(byte[] bytes) {
public static String base64Encode(byte[] bytes) {
return bytes != null ?
Base64.encodeToString(bytes, Base64.NO_WRAP) :
null;
}

/**
* Checks whether a given url is targeting a private endpoint in the Kraken
* API.
*
* @param url The url to check.
* @return Boolean true if targeting a private endpoint, false otherwise.
*/
public static boolean isPrivateRequest(final String url) {
return url.contains("/private/");
}

/**
* Generates a nonce for private requests, or null for public requests.
*
* @return The nonce or null.
*/
public static String generateNonce(final String url) {
return url != null && isPrivateRequest(url) ?
String.format("%-16s", System.currentTimeMillis())
.replace(" ", "0") :
null;
}

/**
* Constructs an encoded request message that Kraken will understand. See
* Kraken API documentation for details.
*
* @param nonce The request nonce. Ignored if null.
* @param data The actual key value pairs to build the message from. Even
* positions are treated as keys and odd positions as values.
* Any null pointer key or value will render the key/value pair
* invalid and hence ignored. Any trailing single keys will
* also be ignored.
* @return The prepared and encoded Kraken message.
*/
public static String composeMessage(final String nonce,
final HashMap<String, String> data) {

Uri.Builder builder = new Uri.Builder();

if (nonce != null)
builder.appendQueryParameter("nonce", nonce);

if (data != null && !data.isEmpty())
for (Map.Entry<String, String> entry : data.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key != null && value != null)
builder.appendQueryParameter(key, value);
}

return builder.build().getEncodedQuery();
}

/**
* Generates the message signature headers. See Kraken API documentation for
* details.
*
* @param key The API key.
* @param secret The corresponding (decoded) key secret.
* @param path The request path.
* @param nonce The request nonce that was used for the message.
* @param message The request message
* @return The Kraken request signature headers. Null if no path is given or
* an empty list if no nonce is given.
*/
public static List<NetworkClient.Header> generateSignatureHeaders(final String key,
final byte[] secret,
final String path,
final String nonce,
final String message) {
if (key == null || secret == null)
return null;

if (path == null)
return null;

if (nonce == null)
return null;

byte[] data = sha256(asBytes(nonce + message));
if (data == null)
return null;

byte[] input = concat(asBytes(path), data);
byte[] signature = hmacSha512(input, secret);
if (signature == null)
return null;

List<NetworkClient.Header> headers = new ArrayList<>(2);
headers.add(new NetworkClient.Header("API-Key", key));
headers.add(new NetworkClient.Header("API-Sign", base64Encode(signature)));

return headers;
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.echsylon.kraken;
package com.echsylon.kraken.request;

import com.echsylon.kraken.Dictionary;
import com.echsylon.kraken.internal.CallCounter;
import com.google.gson.reflect.TypeToken;

/**
Expand All @@ -20,10 +22,10 @@ public class AccountBalanceRequestBuilder extends RequestBuilder<Dictionary<Stri
* @param key The user API key.
* @param secret The corresponding secret.
*/
AccountBalanceRequestBuilder(final CallCounter callCounter,
final String baseUrl,
final String key,
final byte[] secret) {
public AccountBalanceRequestBuilder(final CallCounter callCounter,
final String baseUrl,
final String key,
final byte[] secret) {

super(1, callCounter, key, secret, baseUrl,
"POST", "/0/private/Balance",
Expand Down
Loading

0 comments on commit a6e3c9a

Please sign in to comment.