Розробка API криптобіржі (REST, WebSocket)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка API криптобіржі (REST, WebSocket)
Складний
~1-2 тижні
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1285
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1120
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    588
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    854

Розробка API криптобіржи (REST, WebSocket)

API біржи — це публічний інтерфейс для трейдерів, ботів та третьесторонніх інтеграцій. Його якість визначає привабливість платформи для професійних учасників. Binance, Kraken, OKX стандартизували очікування: REST для управління ордерами та рахунком, WebSocket для реалтайм даних.

Дизайн REST API

Структура Endpoints

Стандартна структура публічного та приватного API:

Public API (без аутентифікації):
GET  /api/v1/markets                     - список торговельних пар
GET  /api/v1/markets/{pair}/ticker       - поточний тикер
GET  /api/v1/markets/{pair}/orderbook    - снімок order book
GET  /api/v1/markets/{pair}/trades       - останні угоди
GET  /api/v1/markets/{pair}/candles      - OHLCV дані

Private API (вимагає аутентифікацію):
GET    /api/v1/account/balances          - баланси
GET    /api/v1/account/orders            - список ордерів
POST   /api/v1/account/orders            - розмістити ордер
DELETE /api/v1/account/orders/{id}       - скасувати ордер
POST   /api/v1/account/orders/cancel-all - скасувати всі ордери
GET    /api/v1/account/trades            - історія угод
GET    /api/v1/account/deposits          - історія депозитів
POST   /api/v1/account/withdrawals       - створити вивід

Аутентифікація

Стандарт для крипто API — підпис запиту HMAC-SHA256:

// Серверна верифікація підпису
func verifySignature(r *http.Request, secret string) bool {
    apiKey := r.Header.Get("X-API-Key")
    timestamp := r.Header.Get("X-Timestamp")
    signature := r.Header.Get("X-Signature")
    
    // Перевірка часової мітки (не старше 5 секунд — захист від replay)
    ts, _ := strconv.ParseInt(timestamp, 10, 64)
    if time.Now().UnixMilli()-ts > 5000 {
        return false
    }
    
    // Строка для підпису: метод + шлях + часова мітка + тіло
    method := r.Method
    path := r.URL.RequestURI()
    body, _ := io.ReadAll(r.Body)
    r.Body = io.NopCloser(bytes.NewBuffer(body)) // відновлюємо тіло
    
    message := method + path + timestamp + string(body)
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(message))
    expected := hex.EncodeToString(mac.Sum(nil))
    
    return hmac.Equal([]byte(signature), []byte(expected))
}

Клієнтська сторона підпису:

import hmac, hashlib, time, requests

def signed_request(method: str, path: str, body: dict = None):
    timestamp = str(int(time.time() * 1000))
    body_str = json.dumps(body) if body else ''
    
    message = method + path + timestamp + body_str
    signature = hmac.new(SECRET.encode(), message.encode(), hashlib.sha256).hexdigest()
    
    headers = {
        'X-API-Key': API_KEY,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
        'Content-Type': 'application/json',
    }
    return requests.request(method, BASE_URL + path, headers=headers, data=body_str)

Формат відповідей

Консистентний формат для всіх endpoints:

// Успіх
{
    "success": true,
    "data": { ... },
    "timestamp": 1700000000000
}

// Помилка
{
    "success": false,
    "error": {
        "code": "INSUFFICIENT_BALANCE",
        "message": "Insufficient balance to place order",
        "details": { "available": "0.5", "required": "1.0" }
    },
    "timestamp": 1700000000000
}

Стандартизовані коди помилок важливі для інтеграторів. Краще INSUFFICIENT_BALANCE ніж 400 Bad Request з текстом.

Rate Limiting

// Rate limiter по API ключу з bucket алгоритмом
type RateLimiter struct {
    redis *redis.Client
}

func (rl *RateLimiter) Check(apiKey string, weight int) error {
    key := "rate_limit:" + apiKey
    
    // Sliding window counter
    now := time.Now().UnixMilli()
    windowStart := now - 60000 // 60 секунд
    
    pipe := rl.redis.Pipeline()
    // Видаляємо застарілі записи
    pipe.ZRemRangeByScore(ctx, key, "0", strconv.FormatInt(windowStart, 10))
    // Додаємо поточний запит
    pipe.ZAdd(ctx, key, redis.Z{Score: float64(now), Member: fmt.Sprintf("%d-%d", now, rand.Int())})
    // Підраховуємо суму ваг
    pipe.ZCard(ctx, key)
    pipe.Expire(ctx, key, 2*time.Minute)
    
    results, _ := pipe.Exec(ctx)
    count := results[2].(*redis.IntCmd).Val()
    
    // Різні ліміти для різних рівнів користувачів
    limit := rl.getUserLimit(apiKey) // 1200 req/min для стандарту
    if int(count) > limit {
        return ErrRateLimitExceeded
    }
    return nil
}

Binance-style система ваг: різні endpoints мають різні "ваги" (GET orderbook = 5, POST order = 1, GET all orders = 10). Дозволяє гнучко управляти навантаженням.

Пагінація

Для історичних даних — cursor-based пагінація (краще чим offset для великих таблиць):

type TradesQuery struct {
    Pair      string    `json:"pair"`
    Limit     int       `json:"limit"`  // макс 1000
    StartTime *int64    `json:"start_time,omitempty"`
    EndTime   *int64    `json:"end_time,omitempty"`
    FromID    *string   `json:"from_id,omitempty"` // cursor
}

func (h *Handler) GetTrades(w http.ResponseWriter, r *http.Request) {
    q := parseTradesQuery(r)
    
    trades, err := h.db.GetTrades(q)
    
    var nextCursor *string
    if len(trades) == q.Limit {
        lastID := trades[len(trades)-1].ID
        nextCursor = &lastID
    }
    
    writeJSON(w, map[string]interface{}{
        "trades":      trades,
        "next_cursor": nextCursor,
        "has_more":    nextCursor != nil,
    })
}

WebSocket API

Архітектура WebSocket сервера

type WSHub struct {
    clients    map[*WSClient]bool
    subscriptions map[string]map[*WSClient]bool  // channel -> clients
    broadcast  chan WSMessage
    register   chan *WSClient
    unregister chan *WSClient
    mu         sync.RWMutex
}

func (h *WSHub) Run() {
    for {
        select {
        case client := <-h.register:
            h.mu.Lock()
            h.clients[client] = true
            h.mu.Unlock()
            
        case client := <-h.unregister:
            h.mu.Lock()
            delete(h.clients, client)
            for _, subs := range h.subscriptions {
                delete(subs, client)
            }
            h.mu.Unlock()
            
        case message := <-h.broadcast:
            h.mu.RLock()
            for client := range h.subscriptions[message.Channel] {
                select {
                case client.send <- message.Data:
                default:
                    // Клієнт повільний, буфер переповнений — дропаємо
                    close(client.send)
                    delete(h.clients, client)
                }
            }
            h.mu.RUnlock()
        }
    }
}

Протокол підписок

// Клієнт → Сервер: підписка
{"op": "subscribe", "channels": ["ticker.BTC-USDT", "orderbook.ETH-USDT.50", "trades.BTC-USDT"]}

// Сервер → Клієнт: підтвердження
{"op": "subscribed", "channels": ["ticker.BTC-USDT", "orderbook.ETH-USDT.50", "trades.BTC-USDT"]}

// Сервер → Клієнт: дані тикера
{
    "channel": "ticker.BTC-USDT",
    "data": {
        "pair": "BTC-USDT",
        "last": "42150.50",
        "bid": "42148.00",
        "ask": "42152.00",
        "volume_24h": "1234.56",
        "change_24h": "+2.35",
        "ts": 1700000000000
    }
}

// Order book diff update
{
    "channel": "orderbook.ETH-USDT.50",
    "type": "diff",
    "seq": 12345,
    "data": {
        "bids": [["2200.00", "5.5"], ["2195.00", "0"]],
        "asks": [["2205.00", "3.2"]]
    }
}

Аутентифікація WebSocket

func (ws *WSClient) HandleAuth(msg AuthMessage) {
    // Верифікуємо підпис як у REST
    if !verifyWSAuth(msg.APIKey, msg.Timestamp, msg.Signature) {
        ws.sendError("AUTH_FAILED", "Invalid signature")
        return
    }
    
    ws.userID = getUserIDByAPIKey(msg.APIKey)
    ws.authenticated = true
    ws.sendSuccess("authenticated")
}

// Після авторизації доступ до приватних каналів
// {"op": "subscribe", "channels": ["orders", "trades", "balances"]}

Heartbeat

func (ws *WSClient) startPingPong() {
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    
    for {
        select {
        case <-ticker.C:
            ws.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
            if err := ws.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
                ws.close()
                return
            }
        case <-ws.done:
            return
        }
    }
}

Клієнт повинен відповісти на Ping протягом 10 секунд. Якщо ні — з'єднання закриється. Клієнти також можуть надсилати {"op": "ping"} для утримання з'єднання.

API документація

OpenAPI 3.0 + Swagger UI — стандарт для REST документації. Документація генерується з анотацій коду або пишеться вручну:

# openapi.yaml
paths:
  /api/v1/account/orders:
    post:
      summary: Place Order
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PlaceOrderRequest'
            example:
              pair: "BTC-USDT"
              side: "buy"
              type: "limit"
              quantity: "0.001"
              price: "42000"
              time_in_force: "GTC"

WebSocket документація — окрема сторінка з описом протоколу підписок, форматів повідомлень та прикладами коду (Python, JavaScript, Go).

SDK для основних мов

Публічне API без SDK — додатковий бар'єр для інтеграції. Мінімальний набір:

# Python SDK
class ExchangeClient:
    def __init__(self, api_key: str, api_secret: str, testnet: bool = False):
        self.base_url = 'https://testnet-api.exchange.com' if testnet else 'https://api.exchange.com'
        self.api_key = api_key
        self.api_secret = api_secret
        self.session = aiohttp.ClientSession()
    
    async def place_order(self, pair: str, side: str, type: str, 
                           quantity: str, price: str = None) -> dict:
        return await self._post('/api/v1/account/orders', {
            'pair': pair, 'side': side, 'type': type,
            'quantity': quantity, 'price': price,
        })
    
    # WebSocket
    async def subscribe_ticker(self, pairs: list[str], callback: Callable):
        async with websockets.connect(self.ws_url) as ws:
            await ws.send(json.dumps({
                'op': 'subscribe',
                'channels': [f'ticker.{p}' for p in pairs]
            }))
            async for msg in ws:
                await callback(json.loads(msg))

Тестування

Testnet обов'язковий. Окрема інфраструктура з тими ж endpoints, але тестовою валютою. Binance, OKX, Kraken — всі надають testnet.

Нагрузкове тестування: k6 або wrk для REST (мета — 10,000 req/sec при p99 latency < 50 мс), artillery для WebSocket (1000+ одночасних з'єднань, перевірка broadcast latency).

Часова шкала

Компонент Часова шкала
Public REST API (5–7 endpoints) 3–4 тижні
Private REST API + HMAC auth 3–4 тижні
WebSocket сервер + підписки 3–4 тижні
Rate limiting + управління API ключами 1–2 тижні
OpenAPI документація 1 тиждень
Python SDK 1–2 тижні
Нагрузкове тестування 1–2 тижні

Повний API з документацією, SDK та тестуванням: 3–4 місяці.