c++restclientboost-asioboost-beast

Kraken API: 400 Bad Request when making AddOrder request using Boost.Beast


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%3D1419646086%26ordertype%3Dlimit%26pair%3DXBTUSD%26price%3D25000.000000%26type%3Dbuy%26volume%3D1.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


Solution

  • 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:

    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 :)