I am trying to make a request to the Kraken API's AddOrder endpoint using Boost.Beast in C .
This is the endpoint I am trying to access : https://docs.kraken.com/rest/#tag/User-Trading/operation/addOrder
I am encoding the request body and passing it to the req_.body() method, but I am still getting a 400 Bad Request error.
Here is the request body and encoded request body I am passing:
request body:
nonce=1419646086&ordertype=limit&pair=XBTUSD&price=25000.000000&type=buy&volume=1.000000
Encoded request body which is passed to req_.body():
nonce=1419646086&ordertype=limit&pair=XBTUSD&price=25000.000000&type=buy&volume=1.000000
The url.string() Outputs this which is the correct endpoint :
https://api.kraken.com/0/private/AddOrder
Here is the raw request and response:
raw request:
POST /0/private/AddOrder HTTP/1.1
API-Sign: vMP6JwOHe4nA hnOfDSMEOUmMmZ2UK9RCk9gpRfYOONXhxZW2/QUrDslZCELJo9/cNJlVyBvC4texDk49fwE6g==
Host: api.kraken.com
User-Agent: Boost.Beast/330
API-Key: Skz2FPOvhvvYrOHi6qMEqmmz
Response :
raw response : HTTP/1.1 400 Bad Request
Server: cloudflare
Date: Tue, 27 Dec 2022 17:25:24 GMT
Content-Type: text/html
Content-Length: 155
Connection: close
CF-RAY: -
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<hr><center>cloudflare</center>
</body>
</html>
As you can see I have included all the necessary headers and properly signed the message (I have verified it in postman and it works).
This is my code using the examples provided in the boost.beast : https://gist.github.com/Naseefabu/0f2315ec9727c3e0821ff941a5b370ce
CodePudding user response:
I'm just chipping away at issues in the code. Your nonce generates a string, which you then convert to a long, but store in an int32 variable. Only to then convert it back to a string. I'd cut the whole mess and avoid truncation issues by using
long generate_nonce() {
return std::chrono::system_clock::now().time_since_epoch() / 1ms;
}
Note how much simpler and more efficient that is. Also, how much more correct, as you can see that int32
fatally corrupts the value: http://coliru.stacked-crooked.com/a/ce30cbd6ec5b089e
That's just the first thing that I looked at, and it becomes way more complex from here. I'm not at convinced that you generate the postdata for signing correctly:
you're using a
std::map
which affects the orderingyou're not using the encoded data - which is likely what you should be doing, since different valid encodings exist for URLs and the server would probably first validate the signature before trying to interpret the parts (for security reasons).
when building urls, prefer to use the library instead of manual string manipulation
nowhere do I see the
nonce=
part in the postdata (maybe I'm overlooking it in the mild spaghetti), but I see it e.g. here
I'll probably keep looking for a bit, but this should get you started.
Update
I spotted another issue:
std::vector<char> sigdigest_b64(2 * sigdigest_len);
EVP_EncodeBlock((unsigned char*)sigdigest_b64.data(), sigdigest, sigdigest_len);
return std::string(sigdigest_b64.data(), sigdigest_b64.size());
Neither the length approximation nor the ignored return value inspire confidence. I'd expect
std::string sigdigest_b64(1 4 * (1 sigdigest_len / 3), '\0');
auto sigdigest_b64_len = EVP_EncodeBlock(
reinterpret_cast<unsigned char*>(sigdigest_b64.data()), sigdigest, sigdigest_len);
sigdigest_b64.resize(sigdigest_b64_len);
return sigdigest_b64;
Or better yet,
// Encode the signature in base64.
return base64_encode({sigdigest, sigdigest_len});
With a suitable helper function that you might not even reimplement yourself:
std::string base64_encode(std::basic_string_view<unsigned char> payload) {
std::string result(1 4 * (1 payload.size() / 3), '\0');
auto n = EVP_EncodeBlock(reinterpret_cast<unsigned char*>(result.data()),
payload.data(), payload.size());
result.resize(n);
return result;
}
Then, have unit tests with that implementation :)