我正在尝试向 bybit api 发送一个简单的 post 请求,但我一直收到 10004 符号错误。 这是回复:
{"ret_code":10004,"ret_msg":"error sign! origin_string[api_key=(my api key)\u0026symbol=BTCUSDT\u0026timestamp=1635967650768]","ext_code":"","ext_info":"","result":null,"time_now":"1635967651.397800"}
这是我用来发送请求的代码。
public async static Task<string> cancelAllOrders()
{
string ts = await GenerateTimeStamp();
string paramString = "api_key=" + apiKey + "&symbol=BTCUSDT" + "timestamp=" + ts;
string sign = CreateSignature(secretKey, paramString);
CancelOrderContent co = new CancelOrderContent(apiKey, "BTCUSDT", ts, sign);
var client = new RestClient(ApiUrl + "/v2/private/order/cancelAll");
var request = new RestRequest();
request.AddJsonBody(co);
var response = client.Post(request);
Trace.WriteLine(response.StatusCode.ToString() + " " + response);
return "";
}
这是我正在为请求正文序列化为 JSON 的类。
public class CancelOrderContent
{
public string api_key;
public string sign;
public string symbol;
public string timestamp;
public CancelOrderContent(string api_key, string symbol, string timestamp,string sign)
{
this.api_key = api_key;
this.symbol = symbol;
this.timestamp = timestamp;
this.sign = sign;
}
}
这是我用来创建签名的代码:
public static string CreateSignature(string secret, string message)
{
var signatureBytes = Hmacsha256(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(message));
return ByteArrayToString(signatureBytes);
}
private static byte[] Hmacsha256(byte[] keyByte, byte[] messageBytes)
{
using (var hash = new HMACSHA256(keyByte))
{
return hash.ComputeHash(messageBytes);
}
}
我已经尝试了各种方法来解决这个问题,但我似乎无法摆脱它。我已经尝试了多个端点,但我仍然遇到同样的错误。
2022/01/17 这仍然有效。
嘿@Vexatious 我在尝试提交订单时遇到了与 bybit-api 类似的问题,并且我一直收到密钥被拒绝,权限不足的错误,即使我知道我正在正确设置我的密钥。
也许ByBit改变了什么?库过时了?谁知道。
我注意到的一件主要事情是,他们要求您 在将签名附加到请求正文之前按字母顺序排列参数。
编辑:更新是因为我认识到 GET 请求同样令人困惑。向下滚动以查看示例 POST 请求。
GET 和 POST 请求的处理方式不同
签名
对于两者:您必须在生成签名之前按字母顺序排列参数。
例如:“aParam=foo&bParam=bar”,
这是你的sign参数处理。
GET REQUESTS:将sign参数附加到QueryString的末尾 和发件人,可能需要使用标题
// Might still need {'Content-Type': 'application/x-www-form-urlencoded'}
// header depending on what request lib you're using.
const url = "https://api-testnet.bybit.com/GET?aParam=foo&bParam=bar&sign=" + sign
POST REQUESTS:要求对象作为 request data 发送(仍以上述 QueryString 的形式发送),而不是类似于 GET 请求的完全构建的 Http 字符串。将 sign 参数添加到您生成签名的 QueryString 的末尾,将其分配给您的请求处理程序数据参数并启动!
我确实想出了一个最小的工作版本,成功地在他们的测试网上发布了现货订单,这是开玩笑的测试。
./bybit.test.ts
test("WORKING BYBIT TRADE.", async () => {
const serverTime:number = (await axios.get(`https://api-testnet.bybit.com/spot/v1/time`)).data
// These need to be within 1000ms of eachother (I'm pree sure, their formula is kinda confusing)
console.log(`Their Timestamp`, serverTime)
console.log(`Our Timestamp`, Date.now())
const queryParams = {
// Alphabetically ordered
// (Sign generation function should deal with unordered params using .sort())
'api_key': bybitApiKey,
qty:10,
recvWindow: 10000,
side:"BUY",
symbol:"ETHUSDT",
timestamp: Date.now(),
type:"MARKET",
}
const queryString = querystring.stringify(queryParams)
const sign = "&sign=" + getSignature(queryParams, bybitSecret)
const fullQuery = queryString + sign
console.log(`FullQuery example`, fullQuery)
// api_key=...&qty=10&recvWindow=10000&side=BUY&symbol=ETHUSDT×tamp=1638371037593&type=MARKET&sign=...
let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
withCredentials: true,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
method: "POST",
data: fullQuery,
})
console.log(`Post Status`, result.status)
console.log(`Post Body`, result.data)
})
/**
Post Status 200
Post Body {
ret_code: 0,
ret_msg: '',
ext_code: null,
ext_info: null,
result: {
accountId: '...',
symbol: 'ETHUSDT',
symbolName: 'ETHUSDT',
orderLinkId: '...',
orderId: '...',
transactTime: '...',
price: '0',
origQty: '10',
executedQty: '0',
status: 'FILLED',
timeInForce: 'GTC',
type: 'MARKET',
side: 'BUY'
}
*/
}
./sign.ts
import crypto from 'crypto'
export function getSignature(parameters: any, secret: string) {
var orderedParams = "";
Object.keys(parameters).sort().forEach(function(key) {
orderedParams += key + "=" + parameters[key] + "&";
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}
希望这有帮助!
我刚刚为 ByBit api 解决了这个问题,但是使用 Javascript 而不是 C#。但是我知道 C#,所以希望这对你有用。
使用 POST 的 ByBit API 端点需要与 GET 请求中相同的数据 HMAC 加密。而不是在查询字符串中签署参数(并附加 &sign=xxxx 到它),你:
这就是你的 C# 类变得棘手的地方。您有带有 sign 属性的 CancelOrderContent。这将使用空白值序列化键“符号”。但是,ByBit API 不会接受,因为签名的数据会有所不同。
你要么必须
或...
,"sign":''
部分。我相信这会奏效,因为我必须这样做才能让它在 JS 中工作。但是,添加符号键/值更容易,因为没有类。
这个的一个变体是使 CancelOrderContent 成为动态类型,在序列化/签名之前,您不会添加“签名”键/值。
注意,当您手动将对象序列化为 JSON(不要使用 paramString)时,理论上序列化器应该与 RestRequest.AddJsonBody() 中使用或配置的序列化器相同
抱歉,我没有 C# 代码,但这应该可以。
谢谢@Kwuasimoto 的详细回答,它引导我朝着正确的方向前进,但是,它对我来说并不是很有效。当将 fullQuery 字符串作为 axios 数据传递时,我收到错误“缺少必需的参数'sign'”,但是当我用 URLSearchParams 替换字符串时
const postData = new URLSearchParams(queryParams);
postData.append("sign", signature);
成功了。这是我最终使用的完整代码。这主要是@Kwuasimoto 对我的一些调整的回答。
import crypto from 'crypto'
import axios from 'axios';
const getSignature(parameters: any, secret: string) => {
let orderedParams = "";
Object.keys(parameters).sort().forEach(function(key) {
orderedParams += key + "=" + parameters[key] + "&";
});
orderedParams = orderedParams.substring(0, orderedParams.length - 1);
return crypto.createHmac('sha256', secret).update(orderedParams).digest('hex');
}
const postSpotOrder = async () => {
const queryParams = {
api_key: bybitApiKey,
qty: 10,
recvWindow: 10000,
side: "BUY",
symbol: "ETHUSDT",
timestamp: Date.now(),
type: "MARKET",
};
const signature = getSignature(queryParams, bybitSecret);
const postData = new URLSearchParams(queryParams);
postData.append("sign", signature);
let result = await axios(`https://api-testnet.bybit.com/spot/v1/order`, {
withCredentials: true,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
method: "POST",
data: postData,
});
console.log(`Post Status`, result.status);
console.log(`Post Body`, result.data);
};
看来你签错了。 我将以 V5 订单创建代码为例。
internal class CreateOrderRequest
{
public readonly string category = "linear";
public readonly string orderLinkId;
public readonly string orderType = "Market";
public readonly string qty;
public readonly string side;
public readonly string symbol = "BTCUSDT";
public CreateOrderRequest(string side, string qty, string orderLinkId)
{
this.side = side;
this.qty = qty;
this.orderLinkId = orderLinkId;
}
}
internal class CreateOrderResponse
{
public int retCode;
public string? retMsg;
public long time;
}
private static readonly HttpClient client = new HttpClient();
private static readonly Random random = new Random();
private static async Task<CreateOrderResponse?> CreateOrder()
{
var time = GetTime();
var recvWindow = 5000;
var orderLinkId = RandomString(16);
var payload = new CreateOrderRequest("Buy", "0.001", orderLinkId);
var payloadStr = JsonConvert.SerializeObject(payload);
var paramStr = $"{time}{apiKey}{recvWindow}{payloadStr}";
var sign = CreateSignature(apiSecret, paramStr);
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.bybit.com/v5/order/create");
request.Headers.Add("X-BAPI-API-KEY", apiKey);
request.Headers.Add("X-BAPI-TIMESTAMP", time.ToString());
request.Headers.Add("X-BAPI-SIGN", sign);
request.Headers.Add("X-BAPI-RECV-WINDOW", recvWindow.ToString());
request.Content = new StringContent(payloadStr, Encoding.UTF8, MediaTypeNames.Application.Json);
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var responseObj = JsonConvert.DeserializeObject<CreateOrderResponse>(responseBody);
return responseObj;
}
private static long GetTime()
{
DateTime utcNow = DateTime.UtcNow;
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
long ts = (long)((utcNow - epoch).TotalMilliseconds);
Console.WriteLine($"Now: {ts}");
return ts;
}
private static string CreateSignature(string secret, string message)
{
var signatureBytes = Hmacsha256(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes(message));
return ByteArrayToString(signatureBytes);
}
private static byte[] Hmacsha256(byte[] keyByte, byte[] messageBytes)
{
using var hash = new HMACSHA256(keyByte);
return hash.ComputeHash(messageBytes);
}
private static string ByteArrayToString(byte[] signatureBytes)
{
return BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();
}
private static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}