ccxt: Binance cancelOrder error

Hi, I’ve experienced the error when canceling the order `

[Exchange Error] binance: unknown error code: {“code”:-1102,“msg”:“Mandatory parameter ‘symbol’ was not sent, was empty/null, or malformed.”} -1102

`

The orderId is Valid, and provided symbol ‘VEN/BTC’

  • OS: mac
  • Programming Language version: JS
  • CCXT version: 1.13.45
  • Exchange: Binance
  • Method: cancelOrder

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Comments: 26 (13 by maintainers)

Most upvoted comments

Good to hear that, Thanks for the explanation and the support for this case too.

@ccasselman no worries…

some require it some do not, guess I’ll have to build that logic wrapping CCXT.

Yeah, we will add more to this aspect, hopefully soon… Until then you will have to choose extra params carefully, cause those are exchange-specific, contrary to the other (unified) arguments of a method.

Could you elaborate a little on this magic snippet?

Sure ) Binance error message says that their server does not see a required parameter in the request:

{"code":-1102,"msg":"Mandatory parameter 'symbol' was not sent, was empty/null, or malformed."}

Though, the symbol parameter was sent, here it is, right there, present in the body, as we saw in your verbose log:

binance DELETE https://api.binance.com/api/v3/order
Request:
...
                                              ↓ here is the symbol
timestamp=1529306968894&recvWindow=10000000&symbol=VENBTC&orderId=34089239&signature=70f38cd29a127ea2ed9448efc2ccf95d8f8d929c34e9d4c110537627b61756d6

In this case we’ve been sending it in the request body, but Binance did not see it.

Basically my guess was that DELETE requests work for Node.js, Python and PHP people, but not for browser people. I needed to find a way to make Binance see those params when a DELETE request is sent from a browser…

Next, what I did is I went to Binance docs and noticed that they can handle DELETE params in two ways:

  • when params are passed in the HTTP request body
  • when params are passed in the URL query

So, I took the default sign implementation for binance and changed the following line:

//                             ↓ added this ↓
if ((method === 'GET') || (method === 'DELETE') || (api === 'wapi')) {

This tells CCXT to send all DELETE request parameters to Binance via URL query instead of the default HTTP request body. And it worked, from your own words. Surprise-surprise. So, now I want to thank you for helping me debug this, and I’m going to upload a fix that would not require any sign overrides on your side (you can safely delete that snippet after you update to the upcoming fixed version). Hope this answers your question.

Sorry, full code looked like this :

initExchange() {
        this.exchange = this.configService.getActiveExchange();
        try {
            this.exchange.sign = function (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
                let url = this.urls['api'][api];
                url += '/' + path;
                if (api === 'wapi')
                url += '.html';
                // v1 special case for userDataStream
                if (path === 'userDataStream') {
                    body = this.urlencode (params);
                    headers = {
                        'X-MBX-APIKEY': this.apiKey,
                        'Content-Type': 'application/x-www-form-urlencoded',
                    };
                } else if ((api === 'private') || (api === 'wapi')) {
                    this.checkRequiredCredentials ();
                    let query = this.urlencode (this.extend ({
                        'timestamp': this.nonce (),
                        'recvWindow': this.options['recvWindow'],
                    }, params));
                    let signature = this.hmac (this.encode (query), this.encode (this.secret));
                    query += '&' + 'signature=' + signature;
                    headers = {
                        'X-MBX-APIKEY': this.apiKey,
                    };
                    if ((method === 'GET') || (method === 'DELETE') || (api === 'wapi')) {
                        url += '?' + query;
                    } else {
                        body = query;
                        headers['Content-Type'] = 'application/x-www-form-urlencoded';
                    }
                } else {
                    if (Object.keys (params).length)
                    url += '?' + this.urlencode (params);
                }
                return { 'url': url, 'method': method, 'body': body, 'headers': headers };
            }

            this.exchangeName = this.exchange.name;
            const key = this.exchange.key;
            const secret = this.exchange.secret;
            this.exchange = new ccxt[this.exchange.name]({
                'options': {
                    'adjustForTimeDifference': true,
                    'verbose': true,
                    'recvWindow': 10000000,
                  }
                });
            this.exchange.apiKey = key;
            this.exchange.verbose = true;
            // this.exchange.enableRateLimit = true;
            this.exchange.secret = secret;
            this.exchange.timeout = 5000;
            this.exchange.userAgent = "Trader2";
        } catch (e) {
            this.handleErrors(e);
        }
    }

async uniCancelOrder(id, symbol) {
let confirmation;
try {
confirmation = await this.exchange.cancelOrder(id, symbol, []);
return confirmation;
} catch (e) {
this.handleErrors(e);
}
}

cancelOrder(id, symbol) {
    return Observable.fromPromise(this.uniCancelOrder(id, symbol));
}