From 1d6f500809ebe23ac98af3d4a1e70761b7648fa6 Mon Sep 17 00:00:00 2001 From: nyg Date: Sat, 9 Mar 2024 01:13:48 +0100 Subject: [PATCH 1/3] WIP --- .../dev/andstuff/kraken/example/Examples.java | 101 +++++++++++------- .../andstuff/kraken/api/neo/KrakenAPI.java | 80 ++++++++++++++ .../api/neo/model/KrakenCredentials.java | 5 + .../kraken/api/neo/model/KrakenException.java | 17 +++ .../kraken/api/neo/model/KrakenResponse.java | 8 ++ .../api/neo/model/endpoint/Endpoint.java | 27 +++++ .../neo/model/endpoint/GenericPostParams.java | 12 +++ .../model/endpoint/JsonPrivateEndpoint.java | 17 +++ .../api/neo/model/endpoint/PostParams.java | 4 + .../neo/model/endpoint/PrivateEndpoint.java | 36 +++++++ .../api/neo/model/endpoint/QueryParams.java | 10 ++ .../endpoint/market/AssetInfoEndpoint.java | 20 ++++ .../endpoint/market/AssetPairEndpoint.java | 20 ++++ .../endpoint/market/JsonPublicEndpoint.java | 20 ++++ .../model/endpoint/market/PublicEndpoint.java | 47 ++++++++ .../endpoint/market/ServerTimeEndpoint.java | 12 +++ .../endpoint/market/SystemStatusEndpoint.java | 12 +++ .../market/params/AssetInfoParams.java | 20 ++++ .../market/params/AssetPairParams.java | 21 ++++ .../market/params/GenericQueryParams.java | 17 +++ .../endpoint/market/response/AssetInfo.java | 13 +++ .../endpoint/market/response/AssetPair.java | 58 ++++++++++ .../endpoint/market/response/AssetStatus.java | 8 ++ .../endpoint/market/response/ServerTime.java | 7 ++ .../market/response/SystemStatus.java | 7 ++ .../api/neo/rest/JDKKrakenRestRequester.java | 73 +++++++++++++ .../api/neo/rest/KrakenRestRequester.java | 8 ++ pom.xml | 11 ++ 28 files changed, 655 insertions(+), 36 deletions(-) create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java diff --git a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java index 91825d8..b988f4d 100644 --- a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java +++ b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java @@ -2,53 +2,82 @@ import static dev.andstuff.kraken.example.ExampleHelper.readPropertiesFromFile; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import com.fasterxml.jackson.databind.JsonNode; import dev.andstuff.kraken.api.KrakenApi; +import dev.andstuff.kraken.api.neo.KrakenAPI; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; public class Examples { - public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException { + public static void main(String[] args) { + + + /* Public endpoint examples */ + + KrakenAPI publicAPI = new KrakenAPI(); + + ServerTime serverTime = publicAPI.serverTime(); + System.out.println(serverTime); + + SystemStatus systemStatus = publicAPI.systemStatus(); + System.out.println(systemStatus); + + Map assets1 = publicAPI.assetInfo(List.of("BTC", "ETH")); + System.out.println(assets1); + + Map assets2 = publicAPI.assetInfo(List.of("DOT", "ADA"), "currency"); + System.out.println(assets2); + + Map pairs1 = publicAPI.assetPairs(List.of("ETH/BTC", "ETH/USD")); + System.out.println(pairs1); + + Map pairs2 = publicAPI.assetPairs(List.of("DOT/USD", "ADA/USD"), AssetPair.Info.MARGIN); + System.out.println(pairs2); + + JsonNode ticker = publicAPI.fetchPublic(KrakenApi.Method.TICKER.name, Map.of("pair", "XBTEUR")); + System.out.println(ticker); + + /* Private endpoint example */ Properties apiKeys = readPropertiesFromFile("/api-keys.properties"); - KrakenApi api = new KrakenApi(); - api.setKey(apiKeys.getProperty("key")); - api.setSecret(apiKeys.getProperty("secret")); - - JsonNode response; - Map input = new HashMap<>(); - - input.put("pair", "XBTEUR"); - response = api.queryPublic(KrakenApi.Method.TICKER, input); - System.out.println(response); - - input.clear(); - input.put("pair", "XBTUSD,XLTCZUSD"); - response = api.queryPublic(KrakenApi.Method.ASSET_PAIRS, input); - System.out.println(response); - - input.clear(); - input.put("asset", "ZEUR"); - response = api.queryPrivate(KrakenApi.Method.BALANCE, input); - System.out.println(response); - - input.clear(); - input.put("ordertype", "limit"); - input.put("type", "sell"); - input.put("volume", "1"); - input.put("pair", "XLTCZUSD"); - input.put("price", "1000"); - input.put("oflags", "post,fciq"); - input.put("validate", "true"); - response = api.queryPrivate(KrakenApi.Method.ADD_ORDER, input); - System.out.println(response); + KrakenAPI api = new KrakenAPI(apiKeys.getProperty("key"), apiKeys.getProperty("secret")); + + // + // JsonNode response; + // Map input = new HashMap<>(); + // + // input.put("pair", "XBTEUR"); + // response = api.queryPublic(KrakenApi.Method.TICKER, input); + // System.out.println(response); + // + // input.clear(); + // input.put("pair", "XBTUSD,XLTCZUSD"); + // response = api.queryPublic(KrakenApi.Method.ASSET_PAIRS, input); + // System.out.println(response); + // + // input.clear(); + // input.put("asset", "ZEUR"); + // response = api.queryPrivate(KrakenApi.Method.BALANCE, input); + // System.out.println(response); + // + // input.clear(); + // input.put("ordertype", "limit"); + // input.put("type", "sell"); + // input.put("volume", "1"); + // input.put("pair", "XLTCZUSD"); + // input.put("price", "1000"); + // input.put("oflags", "post,fciq"); + // input.put("validate", "true"); + // response = api.queryPrivate(KrakenApi.Method.ADD_ORDER, input); + // System.out.println(response); } } diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java b/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java new file mode 100644 index 0000000..075290f --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java @@ -0,0 +1,80 @@ +package dev.andstuff.kraken.api.neo; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; + +import dev.andstuff.kraken.api.neo.model.endpoint.JsonPrivateEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.AssetInfoEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.AssetPairEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.JsonPublicEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.ServerTimeEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.SystemStatusEndpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; +import dev.andstuff.kraken.api.neo.rest.JDKKrakenRestRequester; +import dev.andstuff.kraken.api.neo.rest.KrakenRestRequester; + +public class KrakenAPI { + + private final KrakenRestRequester restRequester; + + public KrakenAPI() { + this(new JDKKrakenRestRequester()); + } + + public KrakenAPI(String key, String secret) { + this(new JDKKrakenRestRequester(key, secret)); + } + + public KrakenAPI(KrakenRestRequester restRequester) { + this.restRequester = restRequester; + } + + /* Public endpoints */ + + public ServerTime serverTime() { + return restRequester.execute(new ServerTimeEndpoint()); + } + + public SystemStatus systemStatus() { + return restRequester.execute(new SystemStatusEndpoint()); + } + + public Map assetInfo(List assets) { + return restRequester.execute(new AssetInfoEndpoint(assets)); + } + + public Map assetInfo(List assets, String assetClass) { + return restRequester.execute(new AssetInfoEndpoint(assets, assetClass)); + } + + public Map assetPairs(List pair) { + return restRequester.execute(new AssetPairEndpoint(pair)); + } + + public Map assetPairs(List pair, AssetPair.Info info) { + return restRequester.execute(new AssetPairEndpoint(pair, info)); + } + + /* Unimplemented endpoints */ + + public JsonNode fetchPublic(String path) { + return restRequester.execute(new JsonPublicEndpoint(path)); + } + + public JsonNode fetchPublic(String path, Map queryParams) { + return restRequester.execute(new JsonPublicEndpoint(path, queryParams)); + } + + public JsonNode fetchPrivate(String path) { + return restRequester.execute(new JsonPrivateEndpoint(path)); + } + + public JsonNode fetchPrivate(String path, Map params) { + return restRequester.execute(new JsonPrivateEndpoint(path, params)); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java new file mode 100644 index 0000000..5648ce3 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java @@ -0,0 +1,5 @@ +package dev.andstuff.kraken.api.neo.model; + +public record KrakenCredentials(String key, + String secret) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java new file mode 100644 index 0000000..6c33b41 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java @@ -0,0 +1,17 @@ +package dev.andstuff.kraken.api.neo.model; + +import java.util.List; + +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class KrakenException extends RuntimeException { + + private final List errors; + + public KrakenException(List errors) { + this.errors = errors; + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java new file mode 100644 index 0000000..50912a8 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java @@ -0,0 +1,8 @@ +package dev.andstuff.kraken.api.neo.model; + +import java.util.List; +import java.util.Optional; + +public record KrakenResponse(List error, + Optional result) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java new file mode 100644 index 0000000..37c2013 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java @@ -0,0 +1,27 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +import java.net.URL; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.type.TypeFactory; + +import dev.andstuff.kraken.api.neo.model.KrakenResponse; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public abstract class Endpoint { + + private final String httpMethod; + private final String path; + private final TypeReference responseType; + + public abstract URL buildURL(); + + public JavaType wrappedResponseType(TypeFactory typeFactory) { + return typeFactory.constructParametricType( + KrakenResponse.class, typeFactory.constructType(responseType.getType())); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java new file mode 100644 index 0000000..91e9fbf --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java @@ -0,0 +1,12 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +import java.util.Map; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GenericPostParams implements PostParams { + + private final Map params; + +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java new file mode 100644 index 0000000..13bbeeb --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java @@ -0,0 +1,17 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +public class JsonPrivateEndpoint extends PrivateEndpoint { + + public JsonPrivateEndpoint(String path) { + this(path, Map.of()); + } + + public JsonPrivateEndpoint(String path, Map postParams) { + super(path, new GenericPostParams(postParams), new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java new file mode 100644 index 0000000..1660754 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java @@ -0,0 +1,4 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +public interface PostParams { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java new file mode 100644 index 0000000..af6d718 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java @@ -0,0 +1,36 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; + +import com.fasterxml.jackson.core.type.TypeReference; + +public class PrivateEndpoint extends Endpoint { + + private final PostParams postParams; + + public PrivateEndpoint(String path, TypeReference responseType) { + this(path, null, responseType); + } + + public PrivateEndpoint(String path, PostParams postParams, TypeReference responseType) { + super("POST", path, responseType); + this.postParams = postParams; + } + + @Override + public URL buildURL() { + try { + return new URI("https://api.kraken.com/0/private/%s".formatted(getPath())).toURL(); + } + catch (MalformedURLException | URISyntaxException e) { + throw new IllegalStateException("Error while building endpoint URL", e); + } + } + + public String sign() { + return "signed"; + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java new file mode 100644 index 0000000..06be2b6 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java @@ -0,0 +1,10 @@ +package dev.andstuff.kraken.api.neo.model.endpoint; + +import java.util.Map; + +public interface QueryParams { + + QueryParams EMPTY = Map::of; + + Map toMap(); +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java new file mode 100644 index 0000000..35359f1 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java @@ -0,0 +1,20 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; + +import dev.andstuff.kraken.api.neo.model.endpoint.market.params.AssetInfoParams; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; + +public class AssetInfoEndpoint extends PublicEndpoint> { + + public AssetInfoEndpoint(List assets) { + this(assets, "currency"); + } + + public AssetInfoEndpoint(List assets, String assetClass) { + super("Assets", new AssetInfoParams(assets, assetClass), new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java new file mode 100644 index 0000000..b4886fb --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java @@ -0,0 +1,20 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; + +import dev.andstuff.kraken.api.neo.model.endpoint.market.params.AssetPairParams; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; + +public class AssetPairEndpoint extends PublicEndpoint> { + + public AssetPairEndpoint(List pairs) { + this(pairs, AssetPair.Info.ALL); + } + + public AssetPairEndpoint(List pairs, AssetPair.Info info) { + super("AssetPairs", new AssetPairParams(pairs, info), new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java new file mode 100644 index 0000000..ccd8d5a --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java @@ -0,0 +1,20 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import java.util.Map; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; + +import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import dev.andstuff.kraken.api.neo.model.endpoint.market.params.GenericQueryParams; + +public class JsonPublicEndpoint extends PublicEndpoint { + + public JsonPublicEndpoint(String path) { + super(path, QueryParams.EMPTY, new TypeReference<>() {}); + } + + public JsonPublicEndpoint(String path, Map queryParams) { + super(path, new GenericQueryParams(queryParams), new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java new file mode 100644 index 0000000..4abe3b6 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java @@ -0,0 +1,47 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import static java.util.stream.Collectors.joining; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import com.fasterxml.jackson.core.type.TypeReference; + +import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; + +public class PublicEndpoint extends Endpoint { + + private final QueryParams queryParams; + + public PublicEndpoint(String path, TypeReference responseType) { + this(path, QueryParams.EMPTY, responseType); + } + + public PublicEndpoint(String path, QueryParams queryParams, TypeReference responseType) { + super("GET", path, responseType); + this.queryParams = queryParams; + } + + @Override + public URL buildURL() { + + // TODO support nested params + String queryString = queryParams.toMap() + .entrySet().stream() + .map(e -> "%s=%s".formatted(e.getKey(), URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))) + .collect(joining("&")); + + try { + String baseURL = "https://api.kraken.com/0/public/%s".formatted(getPath()); + return new URI(baseURL + (queryString.isEmpty() ? "" : "?" + queryString)).toURL(); + } + catch (MalformedURLException | URISyntaxException e) { + throw new IllegalStateException("Error while building endpoint URL", e); + } + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java new file mode 100644 index 0000000..7087e59 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java @@ -0,0 +1,12 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import com.fasterxml.jackson.core.type.TypeReference; + +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; + +public class ServerTimeEndpoint extends PublicEndpoint { + + public ServerTimeEndpoint() { + super("Time", new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java new file mode 100644 index 0000000..589c768 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java @@ -0,0 +1,12 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market; + +import com.fasterxml.jackson.core.type.TypeReference; + +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; + +public class SystemStatusEndpoint extends PublicEndpoint { + + public SystemStatusEndpoint() { + super("SystemStatus", new TypeReference<>() {}); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java new file mode 100644 index 0000000..dfdfc22 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java @@ -0,0 +1,20 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.params; + +import java.util.List; +import java.util.Map; + +import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class AssetInfoParams implements QueryParams { + + private final List assets; + private final String assetClass; + + public Map toMap() { + return Map.of( + "asset", String.join(",", assets), + "aclass", assetClass); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java new file mode 100644 index 0000000..ffc09d1 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java @@ -0,0 +1,21 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.params; + +import java.util.List; +import java.util.Map; + +import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class AssetPairParams implements QueryParams { + + private final List pairs; + private final AssetPair.Info info; + + public Map toMap() { + return Map.of( + "pair", String.join(",", pairs), + "info", info.getValue()); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java new file mode 100644 index 0000000..0259658 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java @@ -0,0 +1,17 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.params; + +import java.util.Map; + +import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class GenericQueryParams implements QueryParams { + + private final Map params; + + @Override + public Map toMap() { + return params; + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java new file mode 100644 index 0000000..1f99363 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java @@ -0,0 +1,13 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.response; + +import java.math.BigDecimal; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record AssetInfo(@JsonProperty("aclass") String assetClass, + @JsonProperty("altname") String alternateName, + @JsonProperty("decimals") int maxDecimals, + @JsonProperty("display_decimals") int displayedDecimals, + @JsonProperty("collateral_value") BigDecimal collateralValue, + AssetStatus status) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java new file mode 100644 index 0000000..6dba262 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java @@ -0,0 +1,58 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.response; + +import java.math.BigDecimal; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public record AssetPair(@JsonProperty("altname") String alternateName, + @JsonProperty("wsname") String webSocketName, + @JsonProperty("aclass_base") String baseAssetClass, + @JsonProperty("base") String baseAsset, + @JsonProperty("aclass_quote") String quoteAssetClass, + @JsonProperty("quote") String quoteAsset, + @JsonProperty("pair_decimals") int pairDecimals, + @JsonProperty("cost_decimals") int costDecimals, + @JsonProperty("lot_decimals") int lotDecimals, + @JsonProperty("lot_multiplier") int lotMultiplier, + @JsonProperty("leverage_buy") List leverageBuy, + @JsonProperty("leverage_sell") List leverageSell, + @JsonProperty("fees") List takerFees, + @JsonProperty("fees_maker") List makerFees, + @JsonProperty("fee_volume_currency") String volumeCurrencyFee, + @JsonProperty("margin_call") int marginCallLevel, + @JsonProperty("margin_stop") int marginStopLevel, + @JsonProperty("ordermin") BigDecimal minimumOrderSize, + @JsonProperty("costmin") BigDecimal minimumOrderCost, + @JsonProperty("tick_size") BigDecimal tickSize, + @JsonProperty("status") Status status, + @JsonProperty("long_position_limit") int maxLongPositionSize, + @JsonProperty("short_position_limit") int maxShortPositionSize) { + + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + public record FeeSchedule(BigDecimal volume, BigDecimal percentage) {} + + @Getter + @RequiredArgsConstructor + public enum Info { + + ALL("info"), + LEVERAGE("leverage"), + FEES("fees"), + MARGIN("margin"); + + private final String value; + } + + public enum Status { + ONLINE, + CANCEL_ONLY, + POST_ONLY, + LIMIT_ONLY, + REDUCE_ONLY + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java new file mode 100644 index 0000000..2ad6e44 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java @@ -0,0 +1,8 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.response; + +public enum AssetStatus { + ENABLED, + DEPOSIT_ONLY, + WITHDRAWAL_ONLY, + FUNDING_TEMPORARILY_DISABLED +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java new file mode 100644 index 0000000..eb3c9df --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java @@ -0,0 +1,7 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ServerTime(@JsonProperty("unixtime") long unixTime, + String rfc1123) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java new file mode 100644 index 0000000..e446ae3 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java @@ -0,0 +1,7 @@ +package dev.andstuff.kraken.api.neo.model.endpoint.market.response; + +import java.time.Instant; + +public record SystemStatus(String status, // TODO enum? + Instant timestamp) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java new file mode 100644 index 0000000..878d81c --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java @@ -0,0 +1,73 @@ +package dev.andstuff.kraken.api.neo.rest; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.net.URL; + +import javax.net.ssl.HttpsURLConnection; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import dev.andstuff.kraken.api.neo.model.KrakenCredentials; +import dev.andstuff.kraken.api.neo.model.KrakenException; +import dev.andstuff.kraken.api.neo.model.KrakenResponse; +import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; +import dev.andstuff.kraken.api.neo.model.endpoint.PrivateEndpoint; + +public class JDKKrakenRestRequester implements KrakenRestRequester { + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .addModules(new JavaTimeModule(), new Jdk8Module()) + .build(); + } + + private final KrakenCredentials credentials; + + public JDKKrakenRestRequester() { + this.credentials = null; + } + + public JDKKrakenRestRequester(String key, String secret) { + this.credentials = new KrakenCredentials(key, secret); + } + + @Override + public T execute(Endpoint endpoint) { + try { + URL url = endpoint.buildURL(); + System.out.printf("Fetching %s%n", url); + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + connection.setRequestMethod(endpoint.getHttpMethod()); + connection.addRequestProperty("User-Agent", "github.com/nyg"); + + if (endpoint instanceof PrivateEndpoint privateEndpoint) { + connection.addRequestProperty("API-Key", credentials.key()); + connection.addRequestProperty("API-Sign", privateEndpoint.postParams().sign()); + + // write POST data to request + connection.setDoOutput(true); + try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream())) { + out.write(privateEndpoint.postParams()); + } + } + + JavaType krakenResponseType = endpoint.wrappedResponseType(OBJECT_MAPPER.getTypeFactory()); + KrakenResponse response = OBJECT_MAPPER.readValue(connection.getInputStream(), krakenResponseType); + return response.result().orElseThrow(() -> new KrakenException(response.error())); + } + catch (IOException e) { + throw new IllegalStateException("Error while making request to Kraken API", e); + } + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java new file mode 100644 index 0000000..06385f7 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java @@ -0,0 +1,8 @@ +package dev.andstuff.kraken.api.neo.rest; + +import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; + +public interface KrakenRestRequester { + + T execute(Endpoint endpoint); +} diff --git a/pom.xml b/pom.xml index ce1c4b5..5ed4471 100644 --- a/pom.xml +++ b/pom.xml @@ -90,6 +90,14 @@ com.fasterxml.jackson.core jackson-databind + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + org.projectlombok lombok @@ -114,9 +122,12 @@ true clean verify exec:exec + chore(release): + true v@{version} + maven-central From 83e2a330cd1c99cf3bfb2acd057e4bccf1a4d401 Mon Sep 17 00:00:00 2001 From: nyg Date: Sat, 9 Mar 2024 22:35:55 +0100 Subject: [PATCH 2/3] Handle private endpoint and misc --- .../dev/andstuff/kraken/example/Examples.java | 57 ++--- .../andstuff/kraken/example/JacksonTest.java | 34 +++ ...ampleHelper.java => PropertiesHelper.java} | 6 +- .../andstuff/kraken/example/TotalRewards.java | 25 +- .../dev/andstuff/kraken/api/ApiRequest.java | 196 ---------------- .../dev/andstuff/kraken/api/KrakenAPI.java | 172 ++++++++++++++ .../dev/andstuff/kraken/api/KrakenApi.java | 216 ------------------ .../dev/andstuff/kraken/api/KrakenUtils.java | 76 ------ .../kraken/api/model/KrakenCredentials.java | 64 ++++++ .../api/{neo => }/model/KrakenException.java | 2 +- .../kraken/api/model/KrakenResponse.java | 15 ++ .../{neo => }/model/endpoint/Endpoint.java | 11 +- .../endpoint/market/AssetInfoEndpoint.java | 7 +- .../endpoint/market/AssetPairEndpoint.java | 7 +- .../endpoint/market/ServerTimeEndpoint.java | 5 +- .../endpoint/market/SystemStatusEndpoint.java | 5 +- .../market/params/AssetInfoParams.java | 4 +- .../market/params/AssetPairParams.java | 6 +- .../endpoint/market/response/AssetInfo.java | 2 +- .../endpoint/market/response/AssetPair.java | 2 +- .../endpoint/market/response/AssetStatus.java | 2 +- .../endpoint/market/response/ServerTime.java | 2 +- .../market/response/SystemStatus.java | 7 + .../endpoint/priv/GenericPostParams.java | 37 +++ .../endpoint/priv}/JsonPrivateEndpoint.java | 2 +- .../api/model/endpoint/priv/PostParams.java | 8 + .../endpoint/priv}/PrivateEndpoint.java | 12 +- .../endpoint/pub}/GenericQueryParams.java | 3 +- .../endpoint/pub}/JsonPublicEndpoint.java | 5 +- .../endpoint/pub}/PublicEndpoint.java | 8 +- .../endpoint/pub}/QueryParams.java | 2 +- .../andstuff/kraken/api/neo/KrakenAPI.java | 80 ------- .../api/neo/model/KrakenCredentials.java | 5 - .../kraken/api/neo/model/KrakenResponse.java | 8 - .../neo/model/endpoint/GenericPostParams.java | 12 - .../api/neo/model/endpoint/PostParams.java | 4 - .../market/response/SystemStatus.java | 7 - .../api/neo/rest/JDKKrakenRestRequester.java | 73 ------ .../api/neo/rest/KrakenRestRequester.java | 8 - .../api/rest/DefaultKrakenRestRequester.java | 103 +++++++++ .../kraken/api/rest/KrakenRestRequester.java | 11 + 41 files changed, 529 insertions(+), 782 deletions(-) create mode 100644 examples/src/main/java/dev/andstuff/kraken/example/JacksonTest.java rename examples/src/main/java/dev/andstuff/kraken/example/{ExampleHelper.java => PropertiesHelper.java} (80%) delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/ApiRequest.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/KrakenAPI.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/KrakenApi.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/KrakenUtils.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/model/KrakenCredentials.java rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/KrakenException.java (86%) create mode 100644 library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/Endpoint.java (80%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/AssetInfoEndpoint.java (62%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/AssetPairEndpoint.java (62%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/ServerTimeEndpoint.java (52%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/SystemStatusEndpoint.java (54%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/params/AssetInfoParams.java (75%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/params/AssetPairParams.java (66%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/response/AssetInfo.java (88%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/response/AssetPair.java (97%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/response/AssetStatus.java (62%) rename library/src/main/java/dev/andstuff/kraken/api/{neo => }/model/endpoint/market/response/ServerTime.java (71%) create mode 100644 library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/GenericPostParams.java rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint => model/endpoint/priv}/JsonPrivateEndpoint.java (89%) create mode 100644 library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PostParams.java rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint => model/endpoint/priv}/PrivateEndpoint.java (84%) rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint/market/params => model/endpoint/pub}/GenericQueryParams.java (68%) rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint/market => model/endpoint/pub}/JsonPublicEndpoint.java (70%) rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint/market => model/endpoint/pub}/PublicEndpoint.java (84%) rename library/src/main/java/dev/andstuff/kraken/api/{neo/model/endpoint => model/endpoint/pub}/QueryParams.java (70%) delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java delete mode 100644 library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/rest/DefaultKrakenRestRequester.java create mode 100644 library/src/main/java/dev/andstuff/kraken/api/rest/KrakenRestRequester.java diff --git a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java index b988f4d..95fae9e 100644 --- a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java +++ b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java @@ -1,6 +1,6 @@ package dev.andstuff.kraken.example; -import static dev.andstuff.kraken.example.ExampleHelper.readPropertiesFromFile; +import static dev.andstuff.kraken.example.PropertiesHelper.readFromFile; import java.util.List; import java.util.Map; @@ -8,12 +8,11 @@ import com.fasterxml.jackson.databind.JsonNode; -import dev.andstuff.kraken.api.KrakenApi; -import dev.andstuff.kraken.api.neo.KrakenAPI; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; +import dev.andstuff.kraken.api.KrakenAPI; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.model.endpoint.market.response.SystemStatus; public class Examples { @@ -42,42 +41,26 @@ public static void main(String[] args) { Map pairs2 = publicAPI.assetPairs(List.of("DOT/USD", "ADA/USD"), AssetPair.Info.MARGIN); System.out.println(pairs2); - JsonNode ticker = publicAPI.fetchPublic(KrakenApi.Method.TICKER.name, Map.of("pair", "XBTEUR")); + JsonNode ticker = publicAPI.query(KrakenAPI.Public.TICKER, Map.of("pair", "XBTEUR")); System.out.println(ticker); /* Private endpoint example */ - Properties apiKeys = readPropertiesFromFile("/api-keys.properties"); + Properties apiKeys = readFromFile("/api-keys.properties"); KrakenAPI api = new KrakenAPI(apiKeys.getProperty("key"), apiKeys.getProperty("secret")); - // - // JsonNode response; - // Map input = new HashMap<>(); - // - // input.put("pair", "XBTEUR"); - // response = api.queryPublic(KrakenApi.Method.TICKER, input); - // System.out.println(response); - // - // input.clear(); - // input.put("pair", "XBTUSD,XLTCZUSD"); - // response = api.queryPublic(KrakenApi.Method.ASSET_PAIRS, input); - // System.out.println(response); - // - // input.clear(); - // input.put("asset", "ZEUR"); - // response = api.queryPrivate(KrakenApi.Method.BALANCE, input); - // System.out.println(response); - // - // input.clear(); - // input.put("ordertype", "limit"); - // input.put("type", "sell"); - // input.put("volume", "1"); - // input.put("pair", "XLTCZUSD"); - // input.put("price", "1000"); - // input.put("oflags", "post,fciq"); - // input.put("validate", "true"); - // response = api.queryPrivate(KrakenApi.Method.ADD_ORDER, input); - // System.out.println(response); + JsonNode balance = api.query(KrakenAPI.Private.BALANCE); + System.out.println(balance); + + JsonNode order = api.query(KrakenAPI.Private.ADD_ORDER, Map.of( + "ordertype", "limit", + "type", "sell", + "volume", "1", + "pair", "XLTCZUSD", + "price", "1000", + "oflags", "post,fciq", + "validate", "true")); + System.out.println(order); } } diff --git a/examples/src/main/java/dev/andstuff/kraken/example/JacksonTest.java b/examples/src/main/java/dev/andstuff/kraken/example/JacksonTest.java new file mode 100644 index 0000000..a9c46ed --- /dev/null +++ b/examples/src/main/java/dev/andstuff/kraken/example/JacksonTest.java @@ -0,0 +1,34 @@ +package dev.andstuff.kraken.example; + +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +public class JacksonTest { + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .addModules(new JavaTimeModule(), new Jdk8Module()) + .build(); + } + + public static void main(String[] args) throws JsonProcessingException { + MyRecord myRecord = OBJECT_MAPPER.readValue("{}", MyRecord.class); + System.out.println(myRecord); + System.out.println(myRecord.result().isEmpty()); + } + + public record MyRecord(Optional result) {} + + public class MyClass {} +} diff --git a/examples/src/main/java/dev/andstuff/kraken/example/ExampleHelper.java b/examples/src/main/java/dev/andstuff/kraken/example/PropertiesHelper.java similarity index 80% rename from examples/src/main/java/dev/andstuff/kraken/example/ExampleHelper.java rename to examples/src/main/java/dev/andstuff/kraken/example/PropertiesHelper.java index 6a32665..fb44f12 100644 --- a/examples/src/main/java/dev/andstuff/kraken/example/ExampleHelper.java +++ b/examples/src/main/java/dev/andstuff/kraken/example/PropertiesHelper.java @@ -8,9 +8,9 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class ExampleHelper { +public final class PropertiesHelper { - public static Properties readPropertiesFromFile(String path) { + public static Properties readFromFile(String path) { try { InputStream stream = Examples.class.getResourceAsStream(path); Properties properties = new Properties(); @@ -18,7 +18,7 @@ public static Properties readPropertiesFromFile(String path) { return properties; } catch (IOException e) { - throw new RuntimeException(String.format("Could not read properties file: %s", path)); + throw new RuntimeException(String.format("Could not read properties from file: %s", path)); } } } diff --git a/examples/src/main/java/dev/andstuff/kraken/example/TotalRewards.java b/examples/src/main/java/dev/andstuff/kraken/example/TotalRewards.java index 7fa3243..d59daaa 100644 --- a/examples/src/main/java/dev/andstuff/kraken/example/TotalRewards.java +++ b/examples/src/main/java/dev/andstuff/kraken/example/TotalRewards.java @@ -1,6 +1,6 @@ package dev.andstuff.kraken.example; -import static dev.andstuff.kraken.example.ExampleHelper.readPropertiesFromFile; +import static dev.andstuff.kraken.example.PropertiesHelper.readFromFile; import static java.util.Arrays.asList; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.groupingBy; @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; -import dev.andstuff.kraken.api.KrakenApi; +import dev.andstuff.kraken.api.KrakenAPI; /** * TODO Group by year @@ -30,23 +30,20 @@ public class TotalRewards { public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InterruptedException { - Properties apiKeys = readPropertiesFromFile("/api-keys.properties"); + Properties apiKeys = readFromFile("/api-keys.properties"); + KrakenAPI api = new KrakenAPI(apiKeys.getProperty("key"), apiKeys.getProperty("secret")); - KrakenApi api = new KrakenApi(); - api.setKey(apiKeys.getProperty("key")); - api.setSecret(apiKeys.getProperty("secret")); - - Map params = new HashMap<>(); - params.put("type", "staking"); - params.put("without_count", "true"); - params.put("ofs", "0"); + Map params = Map.of( + "type", "staking", + "without_count", "true", + "ofs", "0"); Map rewards = new HashMap<>(); boolean hasNext = true; while (hasNext) { - JsonNode response = api.queryPrivate(KrakenApi.Method.LEDGERS, params); + JsonNode response = api.query(KrakenAPI.Private.LEDGERS, params); params.put("ofs", String.valueOf(Integer.parseInt(params.get("ofs")) + 50)); System.out.printf("Fetched %s rewards%n", params.get("ofs")); @@ -107,12 +104,12 @@ public static void main(String[] args) throws IOException, NoSuchAlgorithmExcept System.out.printf("Total USD: %s%n", totalRewardAmountUsd); } - private static BigDecimal fetchRate(String asset, KrakenApi api) { + private static BigDecimal fetchRate(String asset, KrakenAPI api) { try { Map tickerParams = new HashMap<>(); tickerParams.put("pair", asset + "USD"); - JsonNode tickerResponse = api.queryPublic(KrakenApi.Method.TICKER, tickerParams).findValue("result"); + JsonNode tickerResponse = api.query(KrakenAPI.Public.TICKER, tickerParams).findValue("result"); return new BigDecimal(tickerResponse.findValue(tickerResponse.fieldNames().next()).findValue("c").get(0).textValue()); } catch (Exception e) { diff --git a/library/src/main/java/dev/andstuff/kraken/api/ApiRequest.java b/library/src/main/java/dev/andstuff/kraken/api/ApiRequest.java deleted file mode 100644 index 8eca230..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/ApiRequest.java +++ /dev/null @@ -1,196 +0,0 @@ -package dev.andstuff.kraken.api; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Map; -import java.util.Map.Entry; - -import javax.net.ssl.HttpsURLConnection; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Represents an HTTPS request for querying the Kraken API. - * - * @author nyg - */ -class ApiRequest { - - private static final String ERROR_NULL_METHOD = "The API method can't be null."; - private static final String ERROR_NULL_SIGNATURE = "The signature can't be null."; - private static final String ERROR_NULL_KEY = "The key can't be null."; - private static final String ERROR_NO_PARAMETERS = "The parameters can't be null or empty."; - private static final String ERROR_INCOMPLETE_PRIVATE_METHOD = - "A private method request requires the API key, the message signature and the method parameters."; - - private static final String GITHUB_NYG = "github.nyg"; - private static final String REQUEST_API_SIGN = "API-Sign"; - private static final String REQUEST_API_KEY = "API-Key"; - private static final String REQUEST_USER_AGENT = "User-Agent"; - private static final String REQUEST_POST = "POST"; - - private static final String PUBLIC_URL = "https://api.kraken.com/0/public/"; - private static final String PRIVATE_URL = "https://api.kraken.com/0/private/"; - - private static final String AMPERSAND = "&"; - private static final String EQUAL_SIGN = "="; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - /** - * The request URL. - */ - private URL url; - - /** - * The request message signature. - */ - private String signature; - - /** - * The API key. - */ - private String key; - - /** - * The request's POST data. - */ - private StringBuilder postData; - - /** - * Tells whether the API method is public or private. - */ - private boolean isPublic; - - /** - * Executes the request and returns its response. - * - * @return the request's response - * @throws IOException if the underlying {@link HttpsURLConnection} could - * not be set up or executed - */ - public JsonNode execute() throws IOException { - - HttpsURLConnection connection = null; - try { - connection = (HttpsURLConnection) url.openConnection(); - connection.setRequestMethod(REQUEST_POST); - connection.addRequestProperty(REQUEST_USER_AGENT, GITHUB_NYG); - - // set key & signature is method is private - if (!isPublic) { - - if (key == null || signature == null || postData == null) { - throw new IllegalStateException(ERROR_INCOMPLETE_PRIVATE_METHOD); - } - - connection.addRequestProperty(REQUEST_API_KEY, key); - connection.addRequestProperty(REQUEST_API_SIGN, signature); - } - - // write POST data to request - if (postData != null && !postData.toString().isEmpty()) { - - connection.setDoOutput(true); - - try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream())) { - out.write(postData.toString()); - } - } - - // execute request and read response - return OBJECT_MAPPER.readTree(connection.getInputStream()); - } - finally { - if (connection != null) { - connection.disconnect(); - } - } - } - - /** - * Sets the API method of the request. - * - * @param method the API method - * @return the path of the request taking the method into account - */ - public String setMethod(KrakenApi.Method method) { - - if (method == null) { - throw new IllegalArgumentException(ERROR_NULL_METHOD); - } - - isPublic = method.isPublic; - try { - url = new URI((isPublic ? PUBLIC_URL : PRIVATE_URL) + method.name).toURL(); - return url.getPath(); - } - catch (MalformedURLException | URISyntaxException e) { - throw new IllegalStateException("Could not set method URL", e); - } - } - - /** - * Sets the parameters of the API method. Only supports "1-dimension" map. - * Nulls for keys or values are converted to the string "null". - * - * @param parameters a map containing parameter names and values. - * @return the parameters in POST data format, or null if the parameters are - * null or empty - * @throws UnsupportedEncodingException if the named encoding is not - * supported - * @throws IllegalArgumentException if the map is null of empty - */ - public String setParameters(Map parameters) throws UnsupportedEncodingException { - - if (parameters == null || parameters.isEmpty()) { - throw new IllegalArgumentException(ERROR_NO_PARAMETERS); - } - - postData = new StringBuilder(); - for (Entry entry : parameters.entrySet()) { - postData.append(entry.getKey()) - .append(EQUAL_SIGN) - .append(KrakenUtils.urlEncode(entry.getValue())) - .append(AMPERSAND); - } - - return postData.toString(); - } - - /** - * Sets the value of the API-Key request property. - * - * @param key the key - * @throws IllegalArgumentException if the key is null - */ - public void setKey(String key) { - - if (key == null) { - throw new IllegalArgumentException(ERROR_NULL_KEY); - } - - this.key = key; - } - - /** - * Sets the value of the API-Sign request property. - * - * @param signature the signature - * @throws IllegalArgumentException if the signature is null - */ - public void setSignature(String signature) { - - if (signature == null) { - throw new IllegalArgumentException(ERROR_NULL_SIGNATURE); - } - - this.signature = signature; - } -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/KrakenAPI.java b/library/src/main/java/dev/andstuff/kraken/api/KrakenAPI.java new file mode 100644 index 0000000..265eaf4 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/KrakenAPI.java @@ -0,0 +1,172 @@ +package dev.andstuff.kraken.api; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.JsonNode; + +import dev.andstuff.kraken.api.model.endpoint.market.AssetInfoEndpoint; +import dev.andstuff.kraken.api.model.endpoint.market.AssetPairEndpoint; +import dev.andstuff.kraken.api.model.endpoint.market.ServerTimeEndpoint; +import dev.andstuff.kraken.api.model.endpoint.market.SystemStatusEndpoint; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.model.endpoint.market.response.SystemStatus; +import dev.andstuff.kraken.api.model.endpoint.priv.JsonPrivateEndpoint; +import dev.andstuff.kraken.api.model.endpoint.pub.JsonPublicEndpoint; +import dev.andstuff.kraken.api.rest.DefaultKrakenRestRequester; +import dev.andstuff.kraken.api.rest.KrakenRestRequester; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +public class KrakenAPI { + + private final KrakenRestRequester restRequester; + + public KrakenAPI() { + this(new DefaultKrakenRestRequester()); + } + + public KrakenAPI(String key, String secret) { + this(new DefaultKrakenRestRequester(key, secret)); + } + + public KrakenAPI(KrakenRestRequester restRequester) { + this.restRequester = restRequester; + } + + /* Implemented public endpoints */ + + public ServerTime serverTime() { + return restRequester.execute(new ServerTimeEndpoint()); + } + + public SystemStatus systemStatus() { + return restRequester.execute(new SystemStatusEndpoint()); + } + + public Map assetInfo(List assets) { + return restRequester.execute(new AssetInfoEndpoint(assets)); + } + + public Map assetInfo(List assets, String assetClass) { + return restRequester.execute(new AssetInfoEndpoint(assets, assetClass)); + } + + public Map assetPairs(List pair) { + return restRequester.execute(new AssetPairEndpoint(pair)); + } + + public Map assetPairs(List pair, AssetPair.Info info) { + return restRequester.execute(new AssetPairEndpoint(pair, info)); + } + + /* Query unimplemented endpoints */ + + public JsonNode query(Public endpoint) { + return restRequester.execute(new JsonPublicEndpoint(endpoint.getPath())); + } + + public JsonNode query(Public endpoint, Map queryParams) { + return restRequester.execute(new JsonPublicEndpoint(endpoint.getPath(), queryParams)); + } + + public JsonNode queryPublic(String path) { + return restRequester.execute(new JsonPublicEndpoint(path)); + } + + public JsonNode queryPublic(String path, Map queryParams) { + return restRequester.execute(new JsonPublicEndpoint(path, queryParams)); + } + + public JsonNode query(Private endpoint) { + return restRequester.execute(new JsonPrivateEndpoint(endpoint.getPath())); + } + + public JsonNode query(Private endpoint, Map params) { + return restRequester.execute(new JsonPrivateEndpoint(endpoint.getPath(), params)); + } + + public JsonNode queryPrivate(String path) { + return restRequester.execute(new JsonPrivateEndpoint(path)); + } + + public JsonNode queryPrivate(String path, Map params) { + return restRequester.execute(new JsonPrivateEndpoint(path, params)); + } + + /* All endpoints */ + + @Getter + @RequiredArgsConstructor + public enum Public { + + ASSETS("Assets"), + ASSET_PAIRS("AssetPairs"), + DEPTH("Depth"), + OHLC("OHLC"), + SPREAD("Spread"), + SYSTEM_STATUS("SystemStatus"), + TICKER("Ticker"), + TIME("Time"), + TRADES("Trades"); + + private final String path; + } + + @Getter + @RequiredArgsConstructor + public enum Private { + + ACCOUNT_TRANSFER("AccountTransfer"), + ADD_EXPORT("AddExport"), + ADD_ORDER("AddOrder"), + ADD_ORDER_BATCH("AddOrderBatch"), + BALANCE("Balance"), + BALANCE_EX("BalanceEx"), + CANCEL_ALL("CancelAll"), + CANCEL_ALL_ORDERS_AFTER("CancelAllOrdersAfter"), + CANCEL_ORDER("CancelOrder"), + CANCEL_ORDER_BATCH("CancelOrderBatch"), + CLOSED_ORDERS("ClosedOrders"), + CREATE_SUB_ACCOUNT("CreateSubaccount"), + DEPOSIT_ADDRESSES("DepositAddresses"), + DEPOSIT_METHODS("DepositMethods"), + DEPOSIT_STATUS("DepositStatus"), + EARN_ALLOCATE("Earn/Allocate"), + EARN_ALLOCATE_STATUS("Earn/AllocateStatus"), + EARN_ALLOCATIONS("Earn/Allocations"), + EARN_DEALLOCATE("Earn/Deallocate"), + EARN_DEALLOCATE_STATUS("Earn/DeallocateStatus"), + EARN_STRATEGIES("Earn/Strategies"), + EDIT_ORDER("EditOrder"), + EXPORT_STATUS("ExportStatus"), + GET_WEBSOCKETS_TOKEN("GetWebSocketsToken"), + LEDGERS("Ledgers"), + OPEN_ORDERS("OpenOrders"), + OPEN_POSITIONS("OpenPositions"), + QUERY_LEDGERS("QueryLedgers"), + QUERY_ORDERS("QueryOrders"), + QUERY_TRADES("QueryTrades"), + REMOVE_EXPORT("RemoveExport"), + RETRIEVE_EXPORT("RetrieveExport"), + STAKE("Stake"), + STAKING_ASSETS("Staking/Assets"), + STAKING_PENDING("Staking/Pending"), + STAKING_TRANSACTIONS("Staking/Transactions"), + TRADES_HISTORY("TradesHistory"), + TRADE_BALANCE("TradeBalance"), + TRADE_VOLUME("TradeVolume"), + UNSTAKE("Unstake"), + WALLET_TRANSFER("WalletTransfer"), + WITHDRAW("Withdraw"), + WITHDRAW_ADDRESSES("WithdrawAddresses"), + WITHDRAW_CANCEL("WithdrawCancel"), + WITHDRAW_INFO("WithdrawInfo"), + WITHDRAW_METHODS("WithdrawMethods"), + WITHDRAW_STATUS("WithdrawStatus"); + + public final String path; + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/KrakenApi.java b/library/src/main/java/dev/andstuff/kraken/api/KrakenApi.java deleted file mode 100644 index 01cf071..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/KrakenApi.java +++ /dev/null @@ -1,216 +0,0 @@ -package dev.andstuff.kraken.api; - -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.Map; - -import com.fasterxml.jackson.databind.JsonNode; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -/** - * A KrakenApi instance allows querying the Kraken API. - * - * @author nyg - */ -@Getter -@Setter -@NoArgsConstructor -public class KrakenApi { - - private static final String OTP = "otp"; - private static final String NONCE = "nonce"; - private static final String MICRO_SECONDS = "000"; - - private String key; - private String secret; - - /** - * Query a public method of the API with the given parameters. - * - * @param method the API method - * @param parameters the method parameters - * @return the API response - * @throws IllegalArgumentException if the API method is null - * @throws IOException if the request could not be created or executed - */ - public JsonNode queryPublic(Method method, Map parameters) throws IOException { - - ApiRequest request = new ApiRequest(); - request.setMethod(method); - - if (parameters != null) { - request.setParameters(parameters); - } - - return request.execute(); - } - - /** - * Query a public method of the API without any parameters. - * - * @param method the public API method - * @return the API response - * @throws IOException if the request could not be created or executed - */ - public JsonNode queryPublic(Method method) throws IOException { - return queryPublic(method, null); - } - - /** - * Query a private method of the API with the given parameters. - * - * @param method the private API method - * @param otp the one-time password - * @param parameters the method parameters - * @return the API response - * @throws IOException if the request could not be created or executed - * @throws NoSuchAlgorithmException if the SHA-256 or HmacSha512 algorithm - * could not be found - * @throws InvalidKeyException if the HMAC key is invalid - */ - public JsonNode queryPrivate(Method method, String otp, Map parameters) - throws IOException, NoSuchAlgorithmException, InvalidKeyException { - - ApiRequest request = new ApiRequest(); - request.setKey(key); - - // clone parameter map - parameters = parameters == null ? new HashMap<>() : new HashMap<>(parameters); - - // set OTP parameter - if (otp != null) { - parameters.put(OTP, otp); - } - - // generate nonce - String nonce = System.currentTimeMillis() + MICRO_SECONDS; - parameters.put(NONCE, nonce); - - // set the parameters and retrieve the POST data - String postData = request.setParameters(parameters); - - // create SHA-256 hash of the nonce and the POST data - byte[] sha256 = KrakenUtils.sha256(nonce + postData); - - // set the API method and retrieve the path - byte[] path = KrakenUtils.stringToBytes(request.setMethod(method)); - - // decode the API secret, it's the HMAC key - byte[] hmacKey = KrakenUtils.base64Decode(secret); - - // create the HMAC message from the path and the previous hash - byte[] hmacMessage = KrakenUtils.concatArrays(path, sha256); - - // create the HMAC-SHA512 digest, encode it and set it as the request signature - String hmacDigest = KrakenUtils.base64Encode(KrakenUtils.hmacSha512(hmacKey, hmacMessage)); - request.setSignature(hmacDigest); - - return request.execute(); - } - - /** - * @see #queryPrivate(Method, String, Map) - */ - public JsonNode queryPrivate(Method method) throws IOException, InvalidKeyException, NoSuchAlgorithmException { - return queryPrivate(method, null, null); - } - - /** - * @see #queryPrivate(Method, String, Map) - */ - public JsonNode queryPrivate(Method method, String otp) throws IOException, InvalidKeyException, NoSuchAlgorithmException { - return queryPrivate(method, otp, null); - } - - /** - * @see #queryPrivate(Method, String, Map) - */ - public JsonNode queryPrivate(Method method, Map parameters) - throws IOException, InvalidKeyException, NoSuchAlgorithmException { - return queryPrivate(method, null, parameters); - } - - /** - * Represents an API method. - * - * @author nyg - */ - public enum Method { - - /* Public methods */ - ASSET_PAIRS("AssetPairs", true), - ASSETS("Assets", true), - DEPTH("Depth", true), - OHLC("OHLC", true), - SPREAD("Spread", true), - SYSTEM_STATUS("SystemStatus", true), - TICKER("Ticker", true), - TIME("Time", true), - TRADES("Trades", true), - - // TODO Missing methods - // "/private/AccountTransfer", - // "/private/AddExport", - // "/private/AddOrderBatch", - // "/private/BalanceEx", - // "/private/CancelAll", - // "/private/CancelAllOrdersAfter", - // "/private/CancelOrderBatch", - // "/private/CreateSubaccount", - // "/private/DepositMethods", - // "/private/Earn/Allocate", - // "/private/Earn/AllocateStatus", - // "/private/Earn/Allocations", - // "/private/Earn/Deallocate", - // "/private/Earn/DeallocateStatus", - // "/private/Earn/Strategies", - // "/private/EditOrder", - // "/private/ExportStatus", - // "/private/GetWebSocketsToken", - // "/private/RemoveExport", - // "/private/RetrieveExport", - // "/private/Stake", - // "/private/Staking/Assets", - // "/private/Staking/Pending", - // "/private/Staking/Transactions", - // "/private/Unstake", - // "/private/WalletTransfer", - // "/private/WithdrawAddresses", - // "/private/WithdrawMethods", - - /* Private methods */ - ADD_ORDER("AddOrder", false), - BALANCE("Balance", false), - CANCEL_ORDER("CancelOrder", false), - CLOSED_ORDERS("ClosedOrders", false), - DEPOSIT_ADDRESSES("DepositAddresses", false), - DEPOSIT_METHODS("DepositMethods", false), - DEPOSIT_STATUS("DepositStatus", false), - LEDGERS("Ledgers", false), - OPEN_ORDERS("OpenOrders", false), - OPEN_POSITIONS("OpenPositions", false), - QUERY_LEDGERS("QueryLedgers", false), - QUERY_ORDERS("QueryOrders", false), - QUERY_TRADES("QueryTrades", false), - TRADES_HISTORY("TradesHistory", false), - TRADE_BALANCE("TradeBalance", false), - TRADE_VOLUME("TradeVolume", false), - WITHDRAW("Withdraw", false), - WITHDRAW_CANCEL("WithdrawCancel", false), - WITHDRAW_INFO("WithdrawInfo", false), - WITHDRAW_STATUS("WithdrawStatus", false); - - public final String name; - public final boolean isPublic; - - Method(String name, boolean isPublic) { - this.name = name; - this.isPublic = isPublic; - } - } -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/KrakenUtils.java b/library/src/main/java/dev/andstuff/kraken/api/KrakenUtils.java deleted file mode 100644 index 94af149..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/KrakenUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.andstuff.kraken.api; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * Provides basic utilities for hash, byte array and string manipulation. - * - * @author nyg - */ -final class KrakenUtils { - - private static final String ERROR_NULL_INPUT = "Input can't be null."; - private static final String ERROR_NULL_ARRAYS = "Given arrays can't be null."; - - private static final String SHA256 = "SHA-256"; - private static final String HMAC_SHA512 = "HmacSHA512"; - - public static byte[] base64Decode(String input) { - return Base64.getDecoder().decode(input); - } - - public static String base64Encode(byte[] data) { - return Base64.getEncoder().encodeToString(data); - } - - public static byte[] concatArrays(byte[] a, byte[] b) { - - if (a == null || b == null) { - throw new IllegalArgumentException(ERROR_NULL_ARRAYS); - } - - byte[] concat = new byte[a.length + b.length]; - for (int i = 0; i < concat.length; i++) { - concat[i] = i < a.length ? a[i] : b[i - a.length]; - } - - return concat; - } - - public static byte[] hmacSha512(byte[] key, byte[] message) throws NoSuchAlgorithmException, InvalidKeyException { - Mac mac = Mac.getInstance(HMAC_SHA512); - mac.init(new SecretKeySpec(key, HMAC_SHA512)); - return mac.doFinal(message); - } - - public static byte[] sha256(String message) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance(SHA256); - return md.digest(stringToBytes(message)); - } - - public static byte[] stringToBytes(String input) { - - if (input == null) { - throw new IllegalArgumentException(ERROR_NULL_INPUT); - } - - return input.getBytes(StandardCharsets.UTF_8); - } - - public static String urlEncode(String input) throws UnsupportedEncodingException { - return URLEncoder.encode(input, "UTF-8"); - } - - private KrakenUtils() { - throw new IllegalStateException(); - } -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/KrakenCredentials.java b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenCredentials.java new file mode 100644 index 0000000..205d2f9 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenCredentials.java @@ -0,0 +1,64 @@ +package dev.andstuff.kraken.api.model; + +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class KrakenCredentials { + + @Getter + @NonNull + private final String key; + + @NonNull + private final String secret; + + public String sign(URL url, String nonce, String urlEncodedParams) { + + byte[] hmacKey = Base64.getDecoder().decode(secret); + + byte[] sha256 = sha256(nonce + urlEncodedParams); + byte[] hmacMessage = concat(url.getPath().getBytes(StandardCharsets.UTF_8), sha256); + + byte[] hmac = hmacSha512(hmacKey, hmacMessage); + return Base64.getEncoder().encodeToString(hmac); + } + + public static byte[] concat(byte[] a, byte[] b) { + byte[] concat = new byte[a.length + b.length]; + System.arraycopy(a, 0, concat, 0, a.length); + System.arraycopy(b, 0, concat, a.length, b.length); + return concat; + } + + public static byte[] hmacSha512(byte[] key, byte[] message) { + try { + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(new SecretKeySpec(key, "HmacSHA512")); + return mac.doFinal(message); + } + catch (InvalidKeyException | NoSuchAlgorithmException e) { + throw new IllegalStateException("Could not compute HMAC digest"); + } + } + + private static byte[] sha256(String message) { + try { + return MessageDigest.getInstance("SHA-256").digest(message.getBytes(StandardCharsets.UTF_8)); + } + catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Could not compute SHA-256 digest", e); + } + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenException.java similarity index 86% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java rename to library/src/main/java/dev/andstuff/kraken/api/model/KrakenException.java index 6c33b41..5f9dcc3 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenException.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenException.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model; +package dev.andstuff.kraken.api.model; import java.util.List; diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java new file mode 100644 index 0000000..a1417d0 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java @@ -0,0 +1,15 @@ +package dev.andstuff.kraken.api.model; + +import java.util.List; +import java.util.Optional; + +import com.fasterxml.jackson.databind.node.NullNode; + +public record KrakenResponse(List error, + Optional result) { + + public Optional result() { + // TODO some issue with jackson + return result.map(res -> res.equals(NullNode.getInstance()) ? null : res); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/Endpoint.java similarity index 80% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/Endpoint.java index 37c2013..5d1ec4f 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/Endpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/Endpoint.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; +package dev.andstuff.kraken.api.model.endpoint; import java.net.URL; @@ -6,16 +6,19 @@ import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.type.TypeFactory; -import dev.andstuff.kraken.api.neo.model.KrakenResponse; +import dev.andstuff.kraken.api.model.KrakenResponse; import lombok.Getter; import lombok.RequiredArgsConstructor; -@Getter @RequiredArgsConstructor public abstract class Endpoint { + @Getter private final String httpMethod; - private final String path; + + protected final String path; + + @Getter private final TypeReference responseType; public abstract URL buildURL(); diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetInfoEndpoint.java similarity index 62% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetInfoEndpoint.java index 35359f1..69fb6dc 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetInfoEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetInfoEndpoint.java @@ -1,12 +1,13 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.market; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; -import dev.andstuff.kraken.api.neo.model.endpoint.market.params.AssetInfoParams; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.model.endpoint.market.params.AssetInfoParams; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetInfo; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; public class AssetInfoEndpoint extends PublicEndpoint> { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetPairEndpoint.java similarity index 62% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetPairEndpoint.java index b4886fb..697180c 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/AssetPairEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/AssetPairEndpoint.java @@ -1,12 +1,13 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.market; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; -import dev.andstuff.kraken.api.neo.model.endpoint.market.params.AssetPairParams; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.market.params.AssetPairParams; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; public class AssetPairEndpoint extends PublicEndpoint> { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/ServerTimeEndpoint.java similarity index 52% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/ServerTimeEndpoint.java index 7087e59..cb9a20a 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/ServerTimeEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/ServerTimeEndpoint.java @@ -1,8 +1,9 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.market; import com.fasterxml.jackson.core.type.TypeReference; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.model.endpoint.market.response.ServerTime; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; public class ServerTimeEndpoint extends PublicEndpoint { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/SystemStatusEndpoint.java similarity index 54% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/SystemStatusEndpoint.java index 589c768..4636041 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/SystemStatusEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/SystemStatusEndpoint.java @@ -1,8 +1,9 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.market; import com.fasterxml.jackson.core.type.TypeReference; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; +import dev.andstuff.kraken.api.model.endpoint.market.response.SystemStatus; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; public class SystemStatusEndpoint extends PublicEndpoint { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetInfoParams.java similarity index 75% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetInfoParams.java index dfdfc22..6a98b3e 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetInfoParams.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetInfoParams.java @@ -1,9 +1,9 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.params; +package dev.andstuff.kraken.api.model.endpoint.market.params; import java.util.List; import java.util.Map; -import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import dev.andstuff.kraken.api.model.endpoint.pub.QueryParams; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetPairParams.java similarity index 66% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetPairParams.java index ffc09d1..c419c7a 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/AssetPairParams.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/params/AssetPairParams.java @@ -1,10 +1,10 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.params; +package dev.andstuff.kraken.api.model.endpoint.market.params; import java.util.List; import java.util.Map; -import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.market.response.AssetPair; +import dev.andstuff.kraken.api.model.endpoint.pub.QueryParams; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetInfo.java similarity index 88% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetInfo.java index 1f99363..f2985f1 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetInfo.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetInfo.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.response; +package dev.andstuff.kraken.api.model.endpoint.market.response; import java.math.BigDecimal; diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetPair.java similarity index 97% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetPair.java index 6dba262..86144f1 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetPair.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetPair.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.response; +package dev.andstuff.kraken.api.model.endpoint.market.response; import java.math.BigDecimal; import java.util.List; diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetStatus.java similarity index 62% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetStatus.java index 2ad6e44..b382f5a 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/AssetStatus.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/AssetStatus.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.response; +package dev.andstuff.kraken.api.model.endpoint.market.response; public enum AssetStatus { ENABLED, diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/ServerTime.java similarity index 71% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/ServerTime.java index eb3c9df..ab24f33 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/ServerTime.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/ServerTime.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.response; +package dev.andstuff.kraken.api.model.endpoint.market.response; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java new file mode 100644 index 0000000..132bf28 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java @@ -0,0 +1,7 @@ +package dev.andstuff.kraken.api.model.endpoint.market.response; + +import java.time.Instant; + +public record SystemStatus(String status, // TODO could be enum + Instant timestamp) { +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/GenericPostParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/GenericPostParams.java new file mode 100644 index 0000000..9f15aa1 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/GenericPostParams.java @@ -0,0 +1,37 @@ +package dev.andstuff.kraken.api.model.endpoint.priv; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class GenericPostParams implements PostParams { + + private final Map params; + + public GenericPostParams(Map params) { + this.params = new HashMap<>(params); + } + + @Override + public String initNonce() { + String nonce = Long.toString(System.currentTimeMillis()); + params.put("nonce", nonce); + return nonce; + } + + @Override + public String encoded() { + // TODO handle nested props + return params.keySet().stream() + .reduce( + new StringBuilder(), + (postData, key) -> postData.append(key) + .append("=") + .append(URLEncoder.encode(params.get(key), StandardCharsets.UTF_8)) + .append("&"), + StringBuilder::append) + .toString() + .replaceFirst("&$", ""); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/JsonPrivateEndpoint.java similarity index 89% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/JsonPrivateEndpoint.java index 13bbeeb..532d6a4 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/JsonPrivateEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/JsonPrivateEndpoint.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; +package dev.andstuff.kraken.api.model.endpoint.priv; import java.util.Map; diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PostParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PostParams.java new file mode 100644 index 0000000..623fcd7 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PostParams.java @@ -0,0 +1,8 @@ +package dev.andstuff.kraken.api.model.endpoint.priv; + +public interface PostParams { // TODO maybe create an abstract class + + String initNonce(); + + String encoded(); +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PrivateEndpoint.java similarity index 84% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PrivateEndpoint.java index af6d718..d63964a 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PrivateEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/priv/PrivateEndpoint.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; +package dev.andstuff.kraken.api.model.endpoint.priv; import java.net.MalformedURLException; import java.net.URI; @@ -7,6 +7,10 @@ import com.fasterxml.jackson.core.type.TypeReference; +import dev.andstuff.kraken.api.model.endpoint.Endpoint; +import lombok.Getter; + +@Getter public class PrivateEndpoint extends Endpoint { private final PostParams postParams; @@ -23,14 +27,10 @@ public PrivateEndpoint(String path, PostParams postParams, TypeReference resp @Override public URL buildURL() { try { - return new URI("https://api.kraken.com/0/private/%s".formatted(getPath())).toURL(); + return new URI("https://api.kraken.com/0/private/%s".formatted(path)).toURL(); } catch (MalformedURLException | URISyntaxException e) { throw new IllegalStateException("Error while building endpoint URL", e); } } - - public String sign() { - return "signed"; - } } diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/GenericQueryParams.java similarity index 68% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/GenericQueryParams.java index 0259658..23bded6 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/params/GenericQueryParams.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/GenericQueryParams.java @@ -1,8 +1,7 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.params; +package dev.andstuff.kraken.api.model.endpoint.pub; import java.util.Map; -import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/JsonPublicEndpoint.java similarity index 70% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/JsonPublicEndpoint.java index ccd8d5a..5fd62fe 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/JsonPublicEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/JsonPublicEndpoint.java @@ -1,13 +1,10 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.pub; import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; -import dev.andstuff.kraken.api.neo.model.endpoint.market.params.GenericQueryParams; - public class JsonPublicEndpoint extends PublicEndpoint { public JsonPublicEndpoint(String path) { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/PublicEndpoint.java similarity index 84% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/PublicEndpoint.java index 4abe3b6..e3ccb03 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/PublicEndpoint.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/PublicEndpoint.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market; +package dev.andstuff.kraken.api.model.endpoint.pub; import static java.util.stream.Collectors.joining; @@ -11,8 +11,7 @@ import com.fasterxml.jackson.core.type.TypeReference; -import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.QueryParams; +import dev.andstuff.kraken.api.model.endpoint.Endpoint; public class PublicEndpoint extends Endpoint { @@ -30,14 +29,13 @@ public PublicEndpoint(String path, QueryParams queryParams, TypeReference res @Override public URL buildURL() { - // TODO support nested params String queryString = queryParams.toMap() .entrySet().stream() .map(e -> "%s=%s".formatted(e.getKey(), URLEncoder.encode(e.getValue(), StandardCharsets.UTF_8))) .collect(joining("&")); try { - String baseURL = "https://api.kraken.com/0/public/%s".formatted(getPath()); + String baseURL = "https://api.kraken.com/0/public/%s".formatted(path); return new URI(baseURL + (queryString.isEmpty() ? "" : "?" + queryString)).toURL(); } catch (MalformedURLException | URISyntaxException e) { diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/QueryParams.java similarity index 70% rename from library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java rename to library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/QueryParams.java index 06be2b6..f2e8837 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/QueryParams.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/pub/QueryParams.java @@ -1,4 +1,4 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; +package dev.andstuff.kraken.api.model.endpoint.pub; import java.util.Map; diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java b/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java deleted file mode 100644 index 075290f..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/KrakenAPI.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.andstuff.kraken.api.neo; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.databind.JsonNode; - -import dev.andstuff.kraken.api.neo.model.endpoint.JsonPrivateEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.AssetInfoEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.AssetPairEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.JsonPublicEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.ServerTimeEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.SystemStatusEndpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetInfo; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.AssetPair; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.ServerTime; -import dev.andstuff.kraken.api.neo.model.endpoint.market.response.SystemStatus; -import dev.andstuff.kraken.api.neo.rest.JDKKrakenRestRequester; -import dev.andstuff.kraken.api.neo.rest.KrakenRestRequester; - -public class KrakenAPI { - - private final KrakenRestRequester restRequester; - - public KrakenAPI() { - this(new JDKKrakenRestRequester()); - } - - public KrakenAPI(String key, String secret) { - this(new JDKKrakenRestRequester(key, secret)); - } - - public KrakenAPI(KrakenRestRequester restRequester) { - this.restRequester = restRequester; - } - - /* Public endpoints */ - - public ServerTime serverTime() { - return restRequester.execute(new ServerTimeEndpoint()); - } - - public SystemStatus systemStatus() { - return restRequester.execute(new SystemStatusEndpoint()); - } - - public Map assetInfo(List assets) { - return restRequester.execute(new AssetInfoEndpoint(assets)); - } - - public Map assetInfo(List assets, String assetClass) { - return restRequester.execute(new AssetInfoEndpoint(assets, assetClass)); - } - - public Map assetPairs(List pair) { - return restRequester.execute(new AssetPairEndpoint(pair)); - } - - public Map assetPairs(List pair, AssetPair.Info info) { - return restRequester.execute(new AssetPairEndpoint(pair, info)); - } - - /* Unimplemented endpoints */ - - public JsonNode fetchPublic(String path) { - return restRequester.execute(new JsonPublicEndpoint(path)); - } - - public JsonNode fetchPublic(String path, Map queryParams) { - return restRequester.execute(new JsonPublicEndpoint(path, queryParams)); - } - - public JsonNode fetchPrivate(String path) { - return restRequester.execute(new JsonPrivateEndpoint(path)); - } - - public JsonNode fetchPrivate(String path, Map params) { - return restRequester.execute(new JsonPrivateEndpoint(path, params)); - } -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java deleted file mode 100644 index 5648ce3..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenCredentials.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.andstuff.kraken.api.neo.model; - -public record KrakenCredentials(String key, - String secret) { -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java deleted file mode 100644 index 50912a8..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/KrakenResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.andstuff.kraken.api.neo.model; - -import java.util.List; -import java.util.Optional; - -public record KrakenResponse(List error, - Optional result) { -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java deleted file mode 100644 index 91e9fbf..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/GenericPostParams.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; - -import java.util.Map; - -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class GenericPostParams implements PostParams { - - private final Map params; - -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java deleted file mode 100644 index 1660754..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/PostParams.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.andstuff.kraken.api.neo.model.endpoint; - -public interface PostParams { -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java b/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java deleted file mode 100644 index e446ae3..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/model/endpoint/market/response/SystemStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package dev.andstuff.kraken.api.neo.model.endpoint.market.response; - -import java.time.Instant; - -public record SystemStatus(String status, // TODO enum? - Instant timestamp) { -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java deleted file mode 100644 index 878d81c..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/JDKKrakenRestRequester.java +++ /dev/null @@ -1,73 +0,0 @@ -package dev.andstuff.kraken.api.neo.rest; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.net.URL; - -import javax.net.ssl.HttpsURLConnection; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; - -import dev.andstuff.kraken.api.neo.model.KrakenCredentials; -import dev.andstuff.kraken.api.neo.model.KrakenException; -import dev.andstuff.kraken.api.neo.model.KrakenResponse; -import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; -import dev.andstuff.kraken.api.neo.model.endpoint.PrivateEndpoint; - -public class JDKKrakenRestRequester implements KrakenRestRequester { - - private static final ObjectMapper OBJECT_MAPPER; - - static { - OBJECT_MAPPER = JsonMapper.builder() - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .addModules(new JavaTimeModule(), new Jdk8Module()) - .build(); - } - - private final KrakenCredentials credentials; - - public JDKKrakenRestRequester() { - this.credentials = null; - } - - public JDKKrakenRestRequester(String key, String secret) { - this.credentials = new KrakenCredentials(key, secret); - } - - @Override - public T execute(Endpoint endpoint) { - try { - URL url = endpoint.buildURL(); - System.out.printf("Fetching %s%n", url); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - connection.setRequestMethod(endpoint.getHttpMethod()); - connection.addRequestProperty("User-Agent", "github.com/nyg"); - - if (endpoint instanceof PrivateEndpoint privateEndpoint) { - connection.addRequestProperty("API-Key", credentials.key()); - connection.addRequestProperty("API-Sign", privateEndpoint.postParams().sign()); - - // write POST data to request - connection.setDoOutput(true); - try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream())) { - out.write(privateEndpoint.postParams()); - } - } - - JavaType krakenResponseType = endpoint.wrappedResponseType(OBJECT_MAPPER.getTypeFactory()); - KrakenResponse response = OBJECT_MAPPER.readValue(connection.getInputStream(), krakenResponseType); - return response.result().orElseThrow(() -> new KrakenException(response.error())); - } - catch (IOException e) { - throw new IllegalStateException("Error while making request to Kraken API", e); - } - } -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java deleted file mode 100644 index 06385f7..0000000 --- a/library/src/main/java/dev/andstuff/kraken/api/neo/rest/KrakenRestRequester.java +++ /dev/null @@ -1,8 +0,0 @@ -package dev.andstuff.kraken.api.neo.rest; - -import dev.andstuff.kraken.api.neo.model.endpoint.Endpoint; - -public interface KrakenRestRequester { - - T execute(Endpoint endpoint); -} diff --git a/library/src/main/java/dev/andstuff/kraken/api/rest/DefaultKrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/rest/DefaultKrakenRestRequester.java new file mode 100644 index 0000000..e35d659 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/rest/DefaultKrakenRestRequester.java @@ -0,0 +1,103 @@ +package dev.andstuff.kraken.api.rest; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; + +import javax.net.ssl.HttpsURLConnection; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import dev.andstuff.kraken.api.model.KrakenCredentials; +import dev.andstuff.kraken.api.model.KrakenException; +import dev.andstuff.kraken.api.model.KrakenResponse; +import dev.andstuff.kraken.api.model.endpoint.Endpoint; +import dev.andstuff.kraken.api.model.endpoint.priv.PostParams; +import dev.andstuff.kraken.api.model.endpoint.priv.PrivateEndpoint; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; + +/** + * {@link KrakenRestRequester} implementation using {@link HttpsURLConnection}. + */ +public class DefaultKrakenRestRequester implements KrakenRestRequester { + + private static final ObjectMapper OBJECT_MAPPER; + + static { + OBJECT_MAPPER = JsonMapper.builder() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .addModules(new JavaTimeModule(), new Jdk8Module()) + .build(); + } + + private final KrakenCredentials credentials; + + public DefaultKrakenRestRequester() { + this.credentials = null; + } + + public DefaultKrakenRestRequester(String key, String secret) { + this.credentials = new KrakenCredentials(key, secret); + } + + @Override + public T execute(PublicEndpoint endpoint) { + try { + HttpsURLConnection connection = createHttpsConnection(endpoint); + System.out.printf("Fetching %s%n", connection.getURL()); + return parseResponse(connection.getInputStream(), endpoint); + } + catch (IOException e) { + throw new IllegalStateException("Error while making request to Kraken API", e); + } + } + + @Override + public T execute(PrivateEndpoint endpoint) { + + if (credentials == null) { + throw new IllegalStateException("API credentials required"); + } + + PostParams postParams = endpoint.getPostParams(); + String nonce = postParams.initNonce(); // TODO nonce generator + String postData = postParams.encoded(); + + try { + HttpsURLConnection connection = createHttpsConnection(endpoint); + System.out.printf("Fetching %s%n", connection.getURL()); + connection.addRequestProperty("API-Key", credentials.getKey()); + connection.addRequestProperty("API-Sign", credentials.sign(connection.getURL(), nonce, postData)); + connection.setDoOutput(true); + + try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream())) { + out.write(postData); + } + + return parseResponse(connection.getInputStream(), endpoint); + } + catch (IOException e) { + throw new IllegalStateException("Error while making request to Kraken API", e); + } + } + + private static HttpsURLConnection createHttpsConnection(Endpoint endpoint) throws IOException { + HttpsURLConnection connection = (HttpsURLConnection) endpoint.buildURL().openConnection(); + connection.setRequestMethod(endpoint.getHttpMethod()); + connection.addRequestProperty("User-Agent", "github.com/nyg"); + return connection; + } + + private static T parseResponse(InputStream responseStream, Endpoint endpoint) throws IOException { + JavaType krakenResponseType = endpoint.wrappedResponseType(OBJECT_MAPPER.getTypeFactory()); + KrakenResponse response = OBJECT_MAPPER.readValue(responseStream, krakenResponseType); + return response.result().orElseThrow(() -> new KrakenException(response.error())); + } +} diff --git a/library/src/main/java/dev/andstuff/kraken/api/rest/KrakenRestRequester.java b/library/src/main/java/dev/andstuff/kraken/api/rest/KrakenRestRequester.java new file mode 100644 index 0000000..14678c6 --- /dev/null +++ b/library/src/main/java/dev/andstuff/kraken/api/rest/KrakenRestRequester.java @@ -0,0 +1,11 @@ +package dev.andstuff.kraken.api.rest; + +import dev.andstuff.kraken.api.model.endpoint.priv.PrivateEndpoint; +import dev.andstuff.kraken.api.model.endpoint.pub.PublicEndpoint; + +public interface KrakenRestRequester { + + T execute(PublicEndpoint endpoint); + + T execute(PrivateEndpoint endpoint); +} From 83376fe25c3e5fc8bfc61a9dc0fb4be3836fc5ce Mon Sep 17 00:00:00 2001 From: nyg Date: Sun, 10 Mar 2024 00:04:52 +0100 Subject: [PATCH 3/3] Update README, misc changes --- README.md | 101 ++++++++++++++++-- .../dev/andstuff/kraken/example/Examples.java | 3 + .../kraken/api/model/KrakenResponse.java | 2 +- .../market/response/SystemStatus.java | 9 +- 4 files changed, 105 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 88cbab7..d4b030f 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,107 @@ [![Maven Central](https://img.shields.io/maven-central/v/dev.andstuff.kraken/kraken-api)](https://central.sonatype.com/artifact/dev.andstuff.kraken/kraken-api) -Query the [Kraken API][1] in Java. +Query the [Kraken REST API][1] in Java. -### Examples +## Maven -Run the examples with: +```xml + + dev.andstuff.kraken + kraken-api + 1.0.0 + +``` + +## Usage + +> Note: the following code examples are for the current state of the repository and not for the v1.0.0. See [here][3] for v1.0.0 code examples. + +### Public endpoints + +Public endpoints that have been implemented by the library, can be queried in the following way: + +```java +KrakenAPI api = new KrakenAPI(); + +Map assets = api.assetInfo(List.of("BTC", "ETH")); +// {BTC=AssetInfo[assetClass=currency, alternateName=XBT, maxDecimals=10, … + +Map pairs = api.assetPairs(List.of("ETH/BTC", "ETH/USD")); +// {ETH/BTC=AssetPair[alternateName=ETHXBT, webSocketName=ETH/XBT, … +``` + +If the endpoint has not yet been implemented (feel free to submit a PR!), the generic `query` method can be used, which will return a `JsonNode` of the [Jackson][2] deserialization libary: + +```java +JsonNode ticker = api.query(KrakenAPI.Public.TICKER, Map.of("pair", "XBTEUR")); +// {"XXBTZEUR":{"a":["62650.00000","1","1.000"],"b":["62649.90000","6","6.000"], … +``` + +It's also possible to specify the path directly, in case a new endpoint has been added by Kraken and not yet added in the library: + +```java +JsonNode trades = api.queryPublic("Trades", Map.of("pair", "XBTUSD", "count", "1")); +// {"XXBTZUSD":[["68515.60000","0.00029628",1.7100231295628998E9,"s","m","",68007835]], … +``` + +### Private endpoints + +Private endpoints can be queried in the same way as the public ones, but an API key and secret must be provided to the `KrakenAPI` instance: + +```java +KrakenAPI api = new KrakenAPI("my key", "my secret"); + +JsonNode balance = api.query(KrakenAPI.Private.BALANCE); +// {"XXBT":"1234.8396278900", … :) +``` + +If the Kraken API call returns an error, an unchecked exception of type `KrakenException` is thrown: + +```java +// API key doesn't have the permission to create orders +JsonNode order = api.query(KrakenAPI.Private.ADD_ORDER, Map.of( + "ordertype", "limit", "type", "sell", + "volume", "1", "pair", "XLTCZUSD", + "price", "1000", "oflags", "post,fciq", + "validate", "true")); +// Exception in thread "main" KrakenException(errors=[EGeneral:Permission denied]) +``` + +### Custom REST requester + +The current implementation of the library uses the JDK's HttpsURLConnection to make HTTP request. If that doesn't suit your needs and which to use something else (e.g. Spring RestTemplate, Apache HttpComponents, OkHttp), you can implement the KrakenRestRequester interface and pass it to the KrakenAPI constructor: + +```java +public class MyRestTemplateRestRequester implements KrakenRestRequester { + public T execute(PublicEndpoint endpoint) { /* your implementation */ } + public T execute(PrivateEndpoint endpoint) { /* your implementation */ } +} + +KrakenAPI api = new KrakenAPI(MyRestTemplateRestRequest(apiKey, apiSecret)); +``` + +See `DefaultKrakenRestRequester` for the default implementation. + +### Custom nonce generator (not yet implemented) + + + +## Examples + +The `examples` Maven module contains some examples that might be worth checking (e.g. total staking rewards summary). The examples can be run directly from your IDE, or from the command line. + +For private endpoints, you need to rename `api-keys.properties.example` (located in `examples/src/main/resources/`) to `api-keys.properties` and fill in your API keys. ```shell -# input your API keys in api-keys.properties -cp examples/src/main/resources/api-keys.properties.example \ - examples/src/main/resources/api-keys.properties - # build project mvn clean install -# run Examples class +# run example classes mvn -q -pl examples exec:java -Dexec.mainClass=dev.andstuff.kraken.example.Examples +mvn -q -pl examples exec:java -Dexec.mainClass=dev.andstuff.kraken.example.TotalRewards ``` [1]: https://docs.kraken.com/rest/ +[2]: https://github.com/FasterXML/jackson +[3]: https://github.com/nyg/kraken-api-java/blob/v1.0.0/examples/src/main/java/dev/andstuff/kraken/example/Examples.java diff --git a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java index 95fae9e..624c764 100644 --- a/examples/src/main/java/dev/andstuff/kraken/example/Examples.java +++ b/examples/src/main/java/dev/andstuff/kraken/example/Examples.java @@ -44,6 +44,9 @@ public static void main(String[] args) { JsonNode ticker = publicAPI.query(KrakenAPI.Public.TICKER, Map.of("pair", "XBTEUR")); System.out.println(ticker); + JsonNode trades = publicAPI.queryPublic("Trades", Map.of("pair", "XBTUSD", "count", "1")); + System.out.println(trades); + /* Private endpoint example */ Properties apiKeys = readFromFile("/api-keys.properties"); diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java index a1417d0..7373f93 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/KrakenResponse.java @@ -9,7 +9,7 @@ public record KrakenResponse(List error, Optional result) { public Optional result() { - // TODO some issue with jackson + // FIXME looks like an issue with jackson which returns Optional.of(NullNode.instance) instead of Optional.empty return result.map(res -> res.equals(NullNode.getInstance()) ? null : res); } } diff --git a/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java index 132bf28..6978001 100644 --- a/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java +++ b/library/src/main/java/dev/andstuff/kraken/api/model/endpoint/market/response/SystemStatus.java @@ -2,6 +2,13 @@ import java.time.Instant; -public record SystemStatus(String status, // TODO could be enum +public record SystemStatus(Description status, Instant timestamp) { + + enum Description { + ONLINE, + MAINTENANCE, + CANCEL_ONLY, + POST_ONLY + } }