143 lines
5.6 KiB
C#
143 lines
5.6 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Serilog;
|
|
|
|
namespace Buyer
|
|
{
|
|
/// <summary>
|
|
/// Bitget 인증이 필요한 REST API 호출 전용 HTTP 클라이언트.
|
|
///
|
|
/// 서명 방식:
|
|
/// HMAC-SHA256(timestamp + METHOD + requestPath + body) → Base64
|
|
///
|
|
/// 필수 헤더:
|
|
/// ACCESS-KEY, ACCESS-SIGN, ACCESS-TIMESTAMP, ACCESS-PASSPHRASE
|
|
/// </summary>
|
|
public class BitgetAuthClient : IDisposable
|
|
{
|
|
private readonly HttpClient _http;
|
|
private readonly string _accessKey;
|
|
private readonly string _secretKey;
|
|
private readonly string _passphrase;
|
|
|
|
public BitgetAuthClient(ApiKeyInfo key,
|
|
string baseUrl = BitgetEndpoints.BaseUrl,
|
|
int timeoutSec = 10)
|
|
: this(key.AccessKey, key.SecretKey, key.PassphraseKey, baseUrl, timeoutSec)
|
|
{ }
|
|
|
|
public BitgetAuthClient(
|
|
string accessKey, string secretKey, string passphrase,
|
|
string baseUrl = BitgetEndpoints.BaseUrl,
|
|
int timeoutSec = 10)
|
|
{
|
|
_accessKey = accessKey;
|
|
_secretKey = secretKey;
|
|
_passphrase = passphrase;
|
|
|
|
_http = new HttpClient
|
|
{
|
|
BaseAddress = new Uri(baseUrl),
|
|
Timeout = TimeSpan.FromSeconds(timeoutSec)
|
|
};
|
|
_http.DefaultRequestHeaders.Add("User-Agent", "Buyer/1.0");
|
|
_http.DefaultRequestHeaders.Add("locale", "en-US");
|
|
}
|
|
|
|
// ════════════════════════════════════════════════════════════════
|
|
// 공개 메서드
|
|
// ════════════════════════════════════════════════════════════════
|
|
|
|
/// <summary>
|
|
/// 인증 헤더를 포함한 GET 요청.
|
|
/// requestPath는 쿼리스트링 포함 경로 (예: /api/v2/mix/position/all-position?productType=USDT-FUTURES)
|
|
/// </summary>
|
|
public async Task<string> GetAsync(string requestPath,
|
|
CancellationToken ct = default)
|
|
{
|
|
string timestamp = Timestamp();
|
|
string sign = Sign(timestamp, "GET", requestPath, "");
|
|
|
|
var req = new HttpRequestMessage(HttpMethod.Get, requestPath);
|
|
AttachAuthHeaders(req, timestamp, sign);
|
|
|
|
Log.Debug("[Auth] GET {Path}", requestPath);
|
|
return await SendAsync(req, ct);
|
|
}
|
|
|
|
/// <summary>인증 헤더를 포함한 POST 요청.</summary>
|
|
public async Task<string> PostAsync(string requestPath, string jsonBody,
|
|
CancellationToken ct = default)
|
|
{
|
|
string timestamp = Timestamp();
|
|
string sign = Sign(timestamp, "POST", requestPath, jsonBody);
|
|
|
|
var req = new HttpRequestMessage(HttpMethod.Post, requestPath)
|
|
{
|
|
Content = new StringContent(jsonBody, Encoding.UTF8, "application/json")
|
|
};
|
|
AttachAuthHeaders(req, timestamp, sign);
|
|
|
|
Log.Debug("[Auth] POST {Path} body={Body}", requestPath, jsonBody);
|
|
return await SendAsync(req, ct);
|
|
}
|
|
|
|
// ════════════════════════════════════════════════════════════════
|
|
// 내부 헬퍼
|
|
// ════════════════════════════════════════════════════════════════
|
|
|
|
private async Task<string> SendAsync(HttpRequestMessage req,
|
|
CancellationToken ct)
|
|
{
|
|
HttpResponseMessage response;
|
|
string body;
|
|
try
|
|
{
|
|
response = await _http.SendAsync(req, ct);
|
|
body = await response.Content.ReadAsStringAsync(ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "[Auth] HTTP 요청 실패: {Method} {Uri}",
|
|
req.Method, req.RequestUri);
|
|
throw;
|
|
}
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
{
|
|
Log.Error("[Auth] HTTP {Status}: {Body}",
|
|
(int)response.StatusCode, body);
|
|
throw new HttpRequestException(
|
|
$"HTTP {(int)response.StatusCode}: {body}");
|
|
}
|
|
|
|
return body;
|
|
}
|
|
|
|
private void AttachAuthHeaders(HttpRequestMessage req,
|
|
string timestamp, string sign)
|
|
{
|
|
req.Headers.Add("ACCESS-KEY", _accessKey);
|
|
req.Headers.Add("ACCESS-SIGN", sign);
|
|
req.Headers.Add("ACCESS-TIMESTAMP", timestamp);
|
|
req.Headers.Add("ACCESS-PASSPHRASE", _passphrase);
|
|
}
|
|
|
|
/// <summary>HMAC-SHA256 → Base64 서명</summary>
|
|
private string Sign(string timestamp, string method,
|
|
string requestPath, string body)
|
|
{
|
|
string message = timestamp + method.ToUpper() + requestPath + body;
|
|
byte[] key = Encoding.UTF8.GetBytes(_secretKey);
|
|
byte[] data = Encoding.UTF8.GetBytes(message);
|
|
using var hmac = new HMACSHA256(key);
|
|
return Convert.ToBase64String(hmac.ComputeHash(data));
|
|
}
|
|
|
|
private static string Timestamp()
|
|
=> DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString();
|
|
|
|
public void Dispose() => _http.Dispose();
|
|
}
|
|
}
|