I'm trying to call the Kraken API using retrofit. I have a working AsyncHttpClient
version I want to convert to a retrofit version and I have some problem with passing POST parameters.
As stated in the doc it needs :
API-Key
and API-Sign
.nonce
start
and ofs
The AsyncHttpClient version (works) :
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
AsyncHttpClient client = new AsyncHttpClient();
client.addHeader("API-Key", key);
client.addHeader("API-Sign", sign);
client.post("https://api.kraken.com"+ path, params, new JsonHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
// works
}
});
The Retrofit version (EAPI:Invalid key
response) :
String start = String.valueOf(cal.getTimeInMillis() / 1000);
String key = properties.getProperty("KRAKEN_API_PUBLIC_KEY");
String nonce = String.valueOf(System.currentTimeMillis());
String path = "/0/private/TradesHistory";
RequestParams params = new RequestParams();
params.add("nonce", nonce);
params.add("start", start);
params.add("ofs", String.valueOf(offset));
String sign = calculateSignature(path, nonce, params.toString());
KrakenService krakenService = KrakenService.retrofit.create(KrakenService.class);
Call<KrakenTrades> call = krakenService.getTradeHistory(key, sign, nonce, start, String.valueOf(offset));
call.enqueue(new Callback<KrakenTrades>() {
@Override
public void onResponse(@Nullable Call<KrakenTrades> call, @Nullable Response<KrakenTrades> response) {
// EAPI:Invalid key
}
});
The service :
public interface KrakenService {
@FormUrlEncoded
@POST("private/TradesHistory")
Call<KrakenTrades> getTradeHistory(
@Header("API-Key") String apiKey,
@Header("API-Sign") String apiSign,
@Field("nonce") String nonce,
@Field("start") String start,
@Field("ofs") String ofs);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.kraken.com/0/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
Only the call is different, do I miss something ? I've tried using @query
, @Body
, @FieldMap
, @HeaderMap
etc. but cannot manage to make it work.
Edit: I just tried with other API like Poloniex and got the same problem (invalid key), while it still works with basic HTTPClient.
I managed to make it work but I have to admit that I don't get the point...
I activated OkHttp
logs to have a look at the requests. Here is what I got first (these are fake keys don't lose your time -_-) :
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: /Eu6Sth0oa13Tr/ofme07TF4ct+TercW7uU9PxBEQhcpYTflC2c/jEW1BZuamBXco0jlgOzWt8RMh0o6kAE5SA==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511301862261
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (2049ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:04:27 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=dc9e29889eeda633314ca5aaad10ce8291511301865; expires=Wed, 21-Nov-18 22:04:25 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f1d348503e80-ZRH
D/OkHttp: {"error":["EAPI:Invalid key"]}
D/OkHttp: <-- END HTTP (30-byte body)
Now, instead of using params.toString()
as argument to calculate the signature, I just passed manually concatenated values ("start=" + start + "&ofs=" + offset +"&nonce=" + nonce
), and here is what I got :
D/OkHttp: --> POST https://api.kraken.com/0/private/TradesHistory http/1.1
D/OkHttp: Content-Type: application/x-www-form-urlencoded
D/OkHttp: Content-Length: 42
D/OkHttp: API-Sign: DuxyUuHbj4V9WrOpaoBM6Rx3mZluoqJdkg3xBLgc0A/lVotq3WL19VErctz+ugTxi0eRpCI6oBnl95+Lmh9WmQ==
D/OkHttp: API-Key: XnmS2gW2Sr1xR/vnB0ivJuHABdXUnW4bsMTOBMREOlz8xYDh00J+D9i4
D/OkHttp: start=1481587200&ofs=0&nonce=1511302135971
D/OkHttp: --> END POST (42-byte body)
D/OkHttp: <-- 200 https://api.kraken.com/0/private/TradesHistory (1340ms)
D/OkHttp: date: Tue, 21 Nov 2017 22:09:04 GMT
D/OkHttp: content-type: application/json; charset=utf-8
D/OkHttp: set-cookie: __cfduid=d7916fbb113ed6f83b9382ea0a57240731511302143; expires=Wed, 21-Nov-18 22:09:03 GMT; path=/; domain=.kraken.com; HttpOnly
D/OkHttp: vary: Accept-Encoding
D/OkHttp: server: cloudflare-nginx
D/OkHttp: cf-ray: 3c16f89b4e273e62-ZRH
D/OkHttp: {"error":[],"result":{"trades":{...}}}
D/OkHttp: <-- END HTTP (11944-byte body)
See the difference in the sent data? No? There isn't...
But it works and I don't get the invalid key
response anymore.
Another strange thing is that changing the order of the parameters makes it fall back to the invalid key
reponse :
String parameters = "start=" + start + "&ofs=" + offset +"&nonce=" + nonce; //works
String parameters = "start=" + start + "&nonce=" + nonce + "&ofs=" + offset; //doesn't work
I cannot explain this... Maybe encoding issue?
EDIT : OK got it, it seems that parameters are sorted in the request body. Although server does not care about the order of HTTP parameters, HMAC does (of course param1=value1¶m2=value2
will generate a different HMAC than param2=value2¶m1=value1
).
So I have to use the exact same order in the string used to calculate the HMAC than in the request body (I hardcoded the value from the order got from the OkHttp logs but maybe a best way would be to extract it from the request body, or even better canonicalize the request body).