I am getting a signature not valid error while connecting to Binance API endpoints that require authentication. There is a similar query in following link however it is specific to Binance, I guess and this question is specific to Binance US. I tried to use the methods in below link though and it didn't work.
Following is the Python code
import urllib.parse
import hashlib
import hmac
import base64
import requests
api_url = "https://api.binance.us"
# get binanceus signature
def get_binanceus_signature(data, secret):
postdata = urllib.parse.urlencode(data)
message = postdata.encode()
byte_key = bytes(secret, 'UTF-8')
mac = hmac.new(byte_key, message, hashlib.sha256).hexdigest()
return mac
# Attaches auth headers and returns results of a POST request
def binanceus_request(uri_path, data, api_key, api_sec):
headers = {}
headers['X-MBX-APIKEY'] = api_key
signature = get_binanceus_signature(data, api_sec)
params={**data, "signature": signature}
req = requests.get((api_url uri_path), params=params, headers=headers)
return req.text
api_key = "vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A"
secret_key = "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j"
uri_path = "/api/v3/openOrders"
data = {
"symbol": "BTCUSDT",
"timestamp": 1499827319559
}
get_open_order_result = binanceus_request(uri_path, data, api_key, secret_key)
Here is the full dart code.
class BinanceUSRestClient {
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
Future<http.Response> getResponse({
required String secret,
required String apiKey,
required String path,
Map<String, dynamic>? queryParams,
}) async {
//Header
Map<String, String> headers = {};
headers['X-MBX-APIKEY'] = apiKey;
//Params
Map<String, dynamic> params = {};
if (queryParams != null) {
params.addAll(queryParams);
}
params['signature'] = createSignature(secret, queryParams);
params['timestamp'] = timestamp;
final Uri uri = Uri.https('api.binance.us', path, params);
http.Response response = await http.get(
uri,
headers: headers,
);
return response;
}
String createSignature(String secret, Map<String, dynamic>? data) {
final String jsonString = jsonEncode(data);
final List<int> message = utf8.encode(jsonString);
final List<int> key = utf8.encode(secret);
final List<int> mac = Hmac(sha256, key).convert(message).bytes;
final String signature = hex.encode(mac);
return signature;
}
}
If it helps, here is the API documentation. Could someone help me in resolving the error?
CodePudding user response:
Finally!! After spending hours, shammy12's post helped me to figure out what went wrong. All this time I have been trying to create a signature based on a parameter in json format. Although Binance US documentation clearly stated,
totalParams
is defined as thequery string
concatenated with therequest body
this was overlooked in most of the responses I referenced. Even recvWindow
parameter has nothing to do with the invalid signature error. All it took me to debug this issue was figure out a way to format the query as,
String _paramsString = 'timestamp=' timeStamp.toString();
And, this is achieved by using following:
String _paramsString = Uri(queryParameters: baseParams).query;
Here is the full debugged code. Please feel free to let me know if there is anything. I have renamed some variables for better readability and avoid some possible confusions.
import 'dart:convert';
import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
class BinanceRestClient {
final int timeStamp = DateTime.now().millisecondsSinceEpoch;
Future<http.Response> getResponse({
required String secret,
required String apiKey,
required String path,
Map<String, dynamic>? queryParams,
}) async {
//Header
Map<String, String> headers = {};
headers['X-MBX-APIKEY'] = apiKey;
//Base params include the timestamp in milliseconds since epoch
//and query parameters required for API end point
Map<String, dynamic> baseParams = {};
baseParams['timestamp'] = timeStamp.toString();
if (queryParams != null) {
baseParams.addAll(queryParams);
}
//Total params is the combination of base params and signature.
Map<String, dynamic> totalParams = baseParams;
totalParams['signature'] = _createSignature(
secret: secret,
baseParams: baseParams,
);
final Uri uri = Uri.https('api.binance.us', path, totalParams);
final http.Response response = await http.get(
uri,
headers: headers,
);
return response;
}
String _createSignature({
required String secret,
required Map<String, dynamic> baseParams,
}) {
//Important Note: baseParams must be changed to a concatenated string before hashed.
//Failure to do so will result in signature invalid error
//
//i.e., String _paramsString = 'timestamp=' timeStamp.toString();
//
//Following converts a json query to a concatenated string
final String _paramsString = Uri(queryParameters: baseParams).query;
final List<int> _message = utf8.encode(_paramsString);
final List<int> _key = utf8.encode(secret);
final List<int> _mac = Hmac(sha256, _key).convert(_message).bytes;
final String _signature = hex.encode(_mac);
return _signature;
}
}