0

I've been trying to the best of my capability to interprete bitmex's indications for api connection, but this is all I get:

function balanceBitmex() {

  var apiKey = '***';
  var apiSecret = '***';

  var verb = 'get';
  var path = '/api/v1/user/wallet';

  var expires = Number(new Date().getTime() +5).toFixed(0);
  var expires = expires.toString();


  var data = '';
  var data = JSON.stringify(data);
  var currency = '';
  var count= '';
  var concat = verb+path+expires+data;


  var apiSignature = Utilities.computeHmacSha256Signature(apiSecret, concat);
  apiSignature = apiSignature.map(function(e) {
      var v = (e < 0 ? e + 256 : e).toString(16);
      return v.length == 1 ? "0" + v : v;
  }).join("");


  var params = {
    'method': 'get',
    'headers': {'contentType': 'application/json',
                'api-expires': expires,
                'api-key': apiKey,
                'api-signature': apiSignature},
    'muteHttpExceptions': true
  };

  //var params = params + data;

  var url = "https://www.bitmex.com/api/v1/user/wallet?currency=XBt";

  var data = UrlFetchApp.fetch(url, params);
  var data = JSON.parse(data.getContentText());

what is wrong with my script? bitmex api explorer - user wallet

Sourabh Choraria
  • 2,255
  • 25
  • 64
John Galassi
  • 309
  • 2
  • 16

1 Answers1

1

The function Utilities.computeHmacSha256Signature(String, String) takes two parameters, in the following order:

  1. value: The input value to generate a hash for.
  2. key: A key to use to generate the hash with.

In your case, you are essentially encrypting the apiSecret using concat as the key. However, it should be the opposite - the apiSecret should be the key used to encrypt concat. You simply have to change:

var apiSignature = Utilities.computeHmacSha256Signature(apiSecret, concat);

For the following:

var apiSignature = Utilities.computeHmacSha256Signature(concat, apiSecret);

Furthermore, you can use the following code to calculate the example provided in the BitMex documentation and verify it properly works.

function testEncryption() {
  var apiSecret = 'chNOOS4KvNXR_Xq4k4c9qsfoKWvnDecLATCRlcBwyKDYnWgO';
  var verb = 'GET';
  var path = '/api/v1/instrument';
  var expires = 1518064236; 
  var data = '';

  var apiSignature = Utilities.computeHmacSha256Signature(verb + path + expires + data, apiSecret);
  apiSignature = apiSignature.map(function(e) {
      var v = (e < 0 ? e + 256 : e).toString(16);
      return v.length == 1 ? "0" + v : v;
  }).join("");

  Logger.log(apiSignature);
}

The result of running the code above is c7682d435d0cfe87c16098df34ef2eb5a549d4c5a3c2b1f0f77b8af73423bf00, which is the same result shown in the BitMex documentation.

Regarding the code you provided, there are a few more mistakes that have to be addressed in order to make the API work:

  1. verb parameter: The parameter you specified is get, but the API only accepts this parameter as uppercase (GET). Using the former, the signature will be different thus making the request fail.
  2. path querystring: The querystring (in the case of your request currency=XBt) has to be present when computing the signature, but in your code it is not (notice the line var path = '/api/v1/user/wallet';)

Your final code, after fixing all the errors above and cleaning up some unused variables, could look like below:

function balanceBitmex() {
  var apiKey = '***'
  var apiSecret = '***';

  var verb = 'GET';

  var path = '/api/v1/user/wallet?currency=XBt';

  var expires = Math.floor((Date.now() / 1000) + 600).toFixed(0);

  var concat = verb + path + expires;

  var apiSignature = Utilities.computeHmacSha256Signature(concat, apiSecret);
  apiSignature = apiSignature.map(function(e) {
      var v = (e < 0 ? e + 256 : e).toString(16);
      return v.length == 1 ? "0" + v : v;
  }).join("");

  var params = {
    'headers': {
      'api-expires': expires,
      'api-key': apiKey,
      'api-signature': apiSignature
    },
    'muteHttpExceptions': true
  };

  var url = "https://www.bitmex.com/api/v1/user/wallet?currency=XBt";

  var response = UrlFetchApp.fetch(url, params);
  var data = JSON.parse(response.getContentText());

  Logger.log(data);
}
carlesgg97
  • 4,184
  • 1
  • 8
  • 24
  • many thanks for your answer, just saw it!! I'll try your solution straight away and get back to you, thanks. ps: indeed trying out the bitmex example was exactly what I was doing, but of course with a completely different result. – John Galassi Dec 13 '19 at 17:07
  • Not still works. If I manually write ```var expires = 1518064236;``` it produces the same result as Bitmex, ok. But when I try to return the same value from the actual date stored in a cell(```Thu Feb 08 05:30:36 GMT+01:00 2018;```) it produces a slightly different value: ```1518064236000``` instead of ```1518064236```. Those trailing zeroes I believe are the cause to the difference that keeps the code from working. And I wasn't able to remove them since it's a ```string``` and I couldn't apply ```toPrecision()``` for istance. If you even try ```parseInt()``` it gives back an exponential. – John Galassi Dec 13 '19 at 18:00
  • 1
    @John just divide it by 1000 – TheMaster Dec 14 '19 at 06:22
  • I've done that, console log says my ```var expires``` is in the past. I've removed it: signature doesnt work. I guess someone more expert needs to take the script, run it in GAS and try the different outcomes, otherwise we are pretty stuck. – John Galassi Dec 14 '19 at 11:07
  • Hey @JohnGalassi, from BitMex documentation: `api-expires: A UNIX timestamp after which the request is no longer valid. This is to prevent replay attacks.`. This parameter must point to a Date that is in the future (you are using a date in the past, February 8th 2018). Try putting a date in the future ie any day next week. Cheers – carlesgg97 Dec 14 '19 at 11:21
  • @carlesgg97 I am using ```var expires = Math.floor((Date.now() / 1000) +600).toFixed(0);``` or ```var expires = Math.round(new Date().getTime() / 1000) + 60;```. I don't think these are in the past.. and in fact when I use one of these the error no longer is ```var expires in the past``` but rather ```signature not valid, as usual``` .. I repeat, someone more expert has to take the script and run it in GAS, there's too many elements that can go wrong. It can't be pointed out just by looking at it – John Galassi Dec 14 '19 at 11:51
  • 1
    Hello @JohnGalassi, I've had the chance to revisit this issue and test it with my account and I have come up to a few realisations in regards to the API signature computation. Kindly revisit my edited answer as it may be of your interest. – carlesgg97 Dec 16 '19 at 08:55
  • may I know sir what did you change before horing you with the Badge?!! It works! I would like to know so that I can adapt the script to different requestes. Thanks, anyway – John Galassi Dec 16 '19 at 11:42
  • Hello @JohnGalassi, thanks for your kind words. You can see in my answer an explanation of the modification points in regards to your original code snippet. For the rest of the `GET` requests, the code should work fine. If you decide to issue POST requests, you will simply have to change the `verb` variable for `POST` and make sure that `concat` is instead defined as `var concat = verb + path + expires + data;`, where `data` is a JSON string containing the request information. – carlesgg97 Dec 18 '19 at 10:57
  • Are there any other reasons for the signature to be invalid, if the signature is actually correct? I correctly make the bitmex example, it matches, with my code everything is the same, but still it just says:"message":"Signature not valid.","name":"HTTPError" – Daniel Jeney Feb 10 '20 at 17:22