Skip to content

Commit b047f47

Browse files
authored
refactor!: adopt a more object-oriented approach (#8)
* Divide classes into a more object-oriented approach, allowing user to specify how to make HTTP requests * Return value objects instead of JsonNode for some endpoints * Allow querying endpoints with a String path
1 parent ed6487a commit b047f47

35 files changed

+1015
-554
lines changed

README.md

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,107 @@
22

33
[![Maven Central](https://img.shields.io/maven-central/v/dev.andstuff.kraken/kraken-api)](https://central.sonatype.com/artifact/dev.andstuff.kraken/kraken-api)
44

5-
Query the [Kraken API][1] in Java.
5+
Query the [Kraken REST API][1] in Java.
66

7-
### Examples
7+
## Maven
88

9-
Run the examples with:
9+
```xml
10+
<dependency>
11+
<groupId>dev.andstuff.kraken</groupId>
12+
<artifactId>kraken-api</artifactId>
13+
<version>1.0.0</version>
14+
</dependency>
15+
```
16+
17+
## Usage
18+
19+
> 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.
20+
21+
### Public endpoints
22+
23+
Public endpoints that have been implemented by the library, can be queried in the following way:
24+
25+
```java
26+
KrakenAPI api = new KrakenAPI();
27+
28+
Map<String, AssetInfo> assets = api.assetInfo(List.of("BTC", "ETH"));
29+
// {BTC=AssetInfo[assetClass=currency, alternateName=XBT, maxDecimals=10, …
30+
31+
Map<String, AssetPair> pairs = api.assetPairs(List.of("ETH/BTC", "ETH/USD"));
32+
// {ETH/BTC=AssetPair[alternateName=ETHXBT, webSocketName=ETH/XBT, …
33+
```
34+
35+
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:
36+
37+
```java
38+
JsonNode ticker = api.query(KrakenAPI.Public.TICKER, Map.of("pair", "XBTEUR"));
39+
// {"XXBTZEUR":{"a":["62650.00000","1","1.000"],"b":["62649.90000","6","6.000"], …
40+
```
41+
42+
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:
43+
44+
```java
45+
JsonNode trades = api.queryPublic("Trades", Map.of("pair", "XBTUSD", "count", "1"));
46+
// {"XXBTZUSD":[["68515.60000","0.00029628",1.7100231295628998E9,"s","m","",68007835]], …
47+
```
48+
49+
### Private endpoints
50+
51+
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:
52+
53+
```java
54+
KrakenAPI api = new KrakenAPI("my key", "my secret");
55+
56+
JsonNode balance = api.query(KrakenAPI.Private.BALANCE);
57+
// {"XXBT":"1234.8396278900", … :)
58+
```
59+
60+
If the Kraken API call returns an error, an unchecked exception of type `KrakenException` is thrown:
61+
62+
```java
63+
// API key doesn't have the permission to create orders
64+
JsonNode order = api.query(KrakenAPI.Private.ADD_ORDER, Map.of(
65+
"ordertype", "limit", "type", "sell",
66+
"volume", "1", "pair", "XLTCZUSD",
67+
"price", "1000", "oflags", "post,fciq",
68+
"validate", "true"));
69+
// Exception in thread "main" KrakenException(errors=[EGeneral:Permission denied])
70+
```
71+
72+
### Custom REST requester
73+
74+
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:
75+
76+
```java
77+
public class MyRestTemplateRestRequester implements KrakenRestRequester {
78+
public <T> T execute(PublicEndpoint<T> endpoint) { /* your implementation */ }
79+
public <T> T execute(PrivateEndpoint<T> endpoint) { /* your implementation */ }
80+
}
81+
82+
KrakenAPI api = new KrakenAPI(MyRestTemplateRestRequest(apiKey, apiSecret));
83+
```
84+
85+
See `DefaultKrakenRestRequester` for the default implementation.
86+
87+
### Custom nonce generator (not yet implemented)
88+
89+
90+
91+
## Examples
92+
93+
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.
94+
95+
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.
1096

1197
```shell
12-
# input your API keys in api-keys.properties
13-
cp examples/src/main/resources/api-keys.properties.example \
14-
examples/src/main/resources/api-keys.properties
15-
1698
# build project
1799
mvn clean install
18100

19-
# run Examples class
101+
# run example classes
20102
mvn -q -pl examples exec:java -Dexec.mainClass=dev.andstuff.kraken.example.Examples
103+
mvn -q -pl examples exec:java -Dexec.mainClass=dev.andstuff.kraken.example.TotalRewards
21104
```
22105

23106
[1]: https://docs.kraken.com/rest/
107+
[2]: https://github.com/FasterXML/jackson
108+
[3]: https://github.com/nyg/kraken-api-java/blob/v1.0.0/examples/src/main/java/dev/andstuff/kraken/example/Examples.java
Lines changed: 56 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,69 @@
11
package dev.andstuff.kraken.example;
22

3-
import static dev.andstuff.kraken.example.ExampleHelper.readPropertiesFromFile;
3+
import static dev.andstuff.kraken.example.PropertiesHelper.readFromFile;
44

5-
import java.io.IOException;
6-
import java.security.InvalidKeyException;
7-
import java.security.NoSuchAlgorithmException;
8-
import java.util.HashMap;
5+
import java.util.List;
96
import java.util.Map;
107
import java.util.Properties;
118

129
import com.fasterxml.jackson.databind.JsonNode;
1310

14-
import dev.andstuff.kraken.api.KrakenApi;
11+
import dev.andstuff.kraken.api.KrakenAPI;
12+
import dev.andstuff.kraken.api.model.endpoint.market.response.AssetInfo;
13+
import dev.andstuff.kraken.api.model.endpoint.market.response.AssetPair;
14+
import dev.andstuff.kraken.api.model.endpoint.market.response.ServerTime;
15+
import dev.andstuff.kraken.api.model.endpoint.market.response.SystemStatus;
1516

1617
public class Examples {
1718

18-
public static void main(String[] args) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
19-
20-
Properties apiKeys = readPropertiesFromFile("/api-keys.properties");
21-
22-
KrakenApi api = new KrakenApi();
23-
api.setKey(apiKeys.getProperty("key"));
24-
api.setSecret(apiKeys.getProperty("secret"));
25-
26-
JsonNode response;
27-
Map<String, String> input = new HashMap<>();
28-
29-
input.put("pair", "XBTEUR");
30-
response = api.queryPublic(KrakenApi.Method.TICKER, input);
31-
System.out.println(response);
32-
33-
input.clear();
34-
input.put("pair", "XBTUSD,XLTCZUSD");
35-
response = api.queryPublic(KrakenApi.Method.ASSET_PAIRS, input);
36-
System.out.println(response);
37-
38-
input.clear();
39-
input.put("asset", "ZEUR");
40-
response = api.queryPrivate(KrakenApi.Method.BALANCE, input);
41-
System.out.println(response);
42-
43-
input.clear();
44-
input.put("ordertype", "limit");
45-
input.put("type", "sell");
46-
input.put("volume", "1");
47-
input.put("pair", "XLTCZUSD");
48-
input.put("price", "1000");
49-
input.put("oflags", "post,fciq");
50-
input.put("validate", "true");
51-
response = api.queryPrivate(KrakenApi.Method.ADD_ORDER, input);
52-
System.out.println(response);
19+
public static void main(String[] args) {
20+
21+
22+
/* Public endpoint examples */
23+
24+
KrakenAPI publicAPI = new KrakenAPI();
25+
26+
ServerTime serverTime = publicAPI.serverTime();
27+
System.out.println(serverTime);
28+
29+
SystemStatus systemStatus = publicAPI.systemStatus();
30+
System.out.println(systemStatus);
31+
32+
Map<String, AssetInfo> assets1 = publicAPI.assetInfo(List.of("BTC", "ETH"));
33+
System.out.println(assets1);
34+
35+
Map<String, AssetInfo> assets2 = publicAPI.assetInfo(List.of("DOT", "ADA"), "currency");
36+
System.out.println(assets2);
37+
38+
Map<String, AssetPair> pairs1 = publicAPI.assetPairs(List.of("ETH/BTC", "ETH/USD"));
39+
System.out.println(pairs1);
40+
41+
Map<String, AssetPair> pairs2 = publicAPI.assetPairs(List.of("DOT/USD", "ADA/USD"), AssetPair.Info.MARGIN);
42+
System.out.println(pairs2);
43+
44+
JsonNode ticker = publicAPI.query(KrakenAPI.Public.TICKER, Map.of("pair", "XBTEUR"));
45+
System.out.println(ticker);
46+
47+
JsonNode trades = publicAPI.queryPublic("Trades", Map.of("pair", "XBTUSD", "count", "1"));
48+
System.out.println(trades);
49+
50+
/* Private endpoint example */
51+
52+
Properties apiKeys = readFromFile("/api-keys.properties");
53+
54+
KrakenAPI api = new KrakenAPI(apiKeys.getProperty("key"), apiKeys.getProperty("secret"));
55+
56+
JsonNode balance = api.query(KrakenAPI.Private.BALANCE);
57+
System.out.println(balance);
58+
59+
JsonNode order = api.query(KrakenAPI.Private.ADD_ORDER, Map.of(
60+
"ordertype", "limit",
61+
"type", "sell",
62+
"volume", "1",
63+
"pair", "XLTCZUSD",
64+
"price", "1000",
65+
"oflags", "post,fciq",
66+
"validate", "true"));
67+
System.out.println(order);
5368
}
5469
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dev.andstuff.kraken.example;
2+
3+
import java.util.Optional;
4+
5+
import com.fasterxml.jackson.core.JsonProcessingException;
6+
import com.fasterxml.jackson.databind.DeserializationFeature;
7+
import com.fasterxml.jackson.databind.MapperFeature;
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.json.JsonMapper;
10+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
11+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
12+
13+
public class JacksonTest {
14+
15+
private static final ObjectMapper OBJECT_MAPPER;
16+
17+
static {
18+
OBJECT_MAPPER = JsonMapper.builder()
19+
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
20+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
21+
.addModules(new JavaTimeModule(), new Jdk8Module())
22+
.build();
23+
}
24+
25+
public static void main(String[] args) throws JsonProcessingException {
26+
MyRecord myRecord = OBJECT_MAPPER.readValue("{}", MyRecord.class);
27+
System.out.println(myRecord);
28+
System.out.println(myRecord.result().isEmpty());
29+
}
30+
31+
public record MyRecord(Optional<MyClass> result) {}
32+
33+
public class MyClass {}
34+
}

examples/src/main/java/dev/andstuff/kraken/example/ExampleHelper.java renamed to examples/src/main/java/dev/andstuff/kraken/example/PropertiesHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
import lombok.NoArgsConstructor;
99

1010
@NoArgsConstructor(access = AccessLevel.PRIVATE)
11-
public final class ExampleHelper {
11+
public final class PropertiesHelper {
1212

13-
public static Properties readPropertiesFromFile(String path) {
13+
public static Properties readFromFile(String path) {
1414
try {
1515
InputStream stream = Examples.class.getResourceAsStream(path);
1616
Properties properties = new Properties();
1717
properties.load(stream);
1818
return properties;
1919
}
2020
catch (IOException e) {
21-
throw new RuntimeException(String.format("Could not read properties file: %s", path));
21+
throw new RuntimeException(String.format("Could not read properties from file: %s", path));
2222
}
2323
}
2424
}

examples/src/main/java/dev/andstuff/kraken/example/TotalRewards.java

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.andstuff.kraken.example;
22

3-
import static dev.andstuff.kraken.example.ExampleHelper.readPropertiesFromFile;
3+
import static dev.andstuff.kraken.example.PropertiesHelper.readFromFile;
44
import static java.util.Arrays.asList;
55
import static java.util.Comparator.comparing;
66
import static java.util.stream.Collectors.groupingBy;
@@ -21,7 +21,7 @@
2121

2222
import com.fasterxml.jackson.databind.JsonNode;
2323

24-
import dev.andstuff.kraken.api.KrakenApi;
24+
import dev.andstuff.kraken.api.KrakenAPI;
2525

2626
/**
2727
* TODO Group by year
@@ -30,23 +30,20 @@ public class TotalRewards {
3030

3131
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException, InterruptedException {
3232

33-
Properties apiKeys = readPropertiesFromFile("/api-keys.properties");
33+
Properties apiKeys = readFromFile("/api-keys.properties");
34+
KrakenAPI api = new KrakenAPI(apiKeys.getProperty("key"), apiKeys.getProperty("secret"));
3435

35-
KrakenApi api = new KrakenApi();
36-
api.setKey(apiKeys.getProperty("key"));
37-
api.setSecret(apiKeys.getProperty("secret"));
38-
39-
Map<String, String> params = new HashMap<>();
40-
params.put("type", "staking");
41-
params.put("without_count", "true");
42-
params.put("ofs", "0");
36+
Map<String, String> params = Map.of(
37+
"type", "staking",
38+
"without_count", "true",
39+
"ofs", "0");
4340

4441
Map<String, JsonNode> rewards = new HashMap<>();
4542

4643
boolean hasNext = true;
4744
while (hasNext) {
4845

49-
JsonNode response = api.queryPrivate(KrakenApi.Method.LEDGERS, params);
46+
JsonNode response = api.query(KrakenAPI.Private.LEDGERS, params);
5047
params.put("ofs", String.valueOf(Integer.parseInt(params.get("ofs")) + 50));
5148
System.out.printf("Fetched %s rewards%n", params.get("ofs"));
5249

@@ -107,12 +104,12 @@ public static void main(String[] args) throws IOException, NoSuchAlgorithmExcept
107104
System.out.printf("Total USD: %s%n", totalRewardAmountUsd);
108105
}
109106

110-
private static BigDecimal fetchRate(String asset, KrakenApi api) {
107+
private static BigDecimal fetchRate(String asset, KrakenAPI api) {
111108
try {
112109
Map<String, String> tickerParams = new HashMap<>();
113110
tickerParams.put("pair", asset + "USD");
114111

115-
JsonNode tickerResponse = api.queryPublic(KrakenApi.Method.TICKER, tickerParams).findValue("result");
112+
JsonNode tickerResponse = api.query(KrakenAPI.Public.TICKER, tickerParams).findValue("result");
116113
return new BigDecimal(tickerResponse.findValue(tickerResponse.fieldNames().next()).findValue("c").get(0).textValue());
117114
}
118115
catch (Exception e) {

0 commit comments

Comments
 (0)