Search code examples
authenticationgoogle-apps-scripthmacnoncecrypto.com-exchange-api

Invalid_Nonce authenticating on Crypto.com Google Script


For private endpoints of Crypto.com, I am getting the error:

{ id: xx,
  method: 'private/get-order-detail',
  code: 10007,
  message: 'INVALID_NONCE' }

for the following code:

function getOrderStatus() {
  var nonce = Math.floor(new Date().setSeconds(new Date().getSeconds() - 10) / 1000);
  nonce += 1; // Increment nonce for each request, is this the increase?
  var payload = {
    "id": orderId,
    "method": "private/get-order-detail",
    "params": {
      "order_id": orderId
    },
    "nonce": nonce
  };
  var payloadString = JSON.stringify(payload);
  var signature = Utilities.computeHmacSha256Signature(payloadString, secretKey);
  var signatureHex = Utilities.base64Encode(signature).toString();
  var headers = {
    "Content-Type": "application/json",
    "API-Key": apiKey,
    "Signature": signatureHex,
    "Timestamp": nonce
  };
  var options = {
    "method": "post",
    "headers": headers,
    "payload": payloadString,
    "muteHttpExceptions": true
  };
  var response = UrlFetchApp.fetch("https://api.crypto.com/v2/private/get-order-detail", options);
  var data = JSON.parse(response.getContentText());
  console.log(data)
  return data;
}

From my understanding, the nonce value needs to be increasing on every request?


Solution

  • When I saw the official document of Crypto.com Spot Exchange V2.1 API for Exchange reference documentation,

    nonce long Y Current timestamp (milliseconds since the Unix epoch)

    It seems that the sample value of nonce is 1587846358253.

    When I saw your script using new Date('2020-04-26T05:25:58.253Z'), 1587878748 is returned as nonce. And, the document says about code: 10007, as 10007 400 INVALID_NONCE Nonce value differs by more than 30 seconds from server.

    If my understanding is correct, I thought that this might be the reason for your current issue. If my understanding is correct, how about the following modification?

    From:

    var nonce = Math.floor(new Date().setSeconds(new Date().getSeconds() - 10) / 1000);
    

    To:

    var nonce = new Date().setSeconds(new Date().getSeconds() - 10);
    

    or

    var nonce = new Date().getTime();
    

    or

    var nonce = new Date().getTime().toString();
    

    Note:

    • In this answer, it supposes that your other part except for nonce correctly works for using the API you want to use. Please be careful about this.

    Reference:

    Added:

    From your following reply,

    with the change it gives a permission error code: 10002, message: 'UNAUTHORIZED' }. I know the API keys are correct.

    I could confirm that your issue of nonce in your question was resolved. And also, I confirmed that your other part script except for nonce was not correct, and, this is your new issue. In this case, I think that it is required to modify your request. I think that your issue of nonce of this question can be resolved by my answer. So, how about modifying your request as follows?

    Modified script:

    The function signRequest is from https://exchange-docs.crypto.com/spot/index.html#request-format.

    function myFunction() {
      // Please set your valid values.
      const nonce = new Date().getTime();
      const id = "###"; // Please set your ID.
      const orderId = "###"; // Please set your order ID.
      const apiKey = "###"; // Please set your API key.
      const apiSecret = "###"; // Please set your secret.
    
    
      const signRequest = (request_body, api_key, secret) => {
        const { id, method, params, nonce } = request_body;
        function isObject(obj) { return obj !== undefined && obj !== null && obj.constructor == Object; }
        function isArray(obj) { return obj !== undefined && obj !== null && obj.constructor == Array; }
        function arrayToString(obj) { return obj.reduce((a, b) => { return a + (isObject(b) ? objectToString(b) : (isArray(b) ? arrayToString(b) : b)); }, ""); }
        function objectToString(obj) { return (obj == null ? "" : Object.keys(obj).sort().reduce((a, b) => { return a + b + (isArray(obj[b]) ? arrayToString(obj[b]) : (isObject(obj[b]) ? objectToString(obj[b]) : obj[b])); }, "")); }
        const paramsString = objectToString(params);
        console.log(paramsString);
        const sigPayload = method + id + api_key + paramsString + nonce;
        request_body.sig = Utilities.computeHmacSha256Signature(sigPayload, secret).map(byte => ('0' + (byte & 0xFF).toString(16)).slice(-2)).join('');
        return request_body;
      };
    
      let request = {
        id: id,
        method: "private/get-order-detail",
        api_key: apiKey,
        params: { order_id: orderId },
        nonce: nonce,
      };
      const requestBody = JSON.stringify(signRequest(request, apiKey, apiSecret));
      const options = {
        method: "post",
        contentType: "application/json",
        payload: requestBody,
      }
    
      var response = UrlFetchApp.fetch("https://api.crypto.com/v2/private/get-order-detail", options);
      var data = JSON.parse(response.getContentText());
      console.log(data)
      return data;
    }