@blackcube/blofin-sdk
v0.56.0
Published
TypeScript SDK for the BloFin exchange (perpetuals CEX): REST v1, WebSocket, HMAC-SHA256 signing
Maintainers
Readme
@blackcube/blofin-sdk
TypeScript SDK pour l'exchange BloFin — perpetuals. Même surface que
@blackcube/hyperliquid-sdk et @blackcube/pacifica-sdk.
BloFin compte en contrats, ce SDK compte en unités de base. C'est la seule différence de fond avec les autres venues, et elle est entièrement absorbée ici — voir Contrats et unités de base.
Installation
pnpm add @blackcube/blofin-sdkNode.js ≥ 22.
Tout passe par la classe Blofin
Tu n'appelles jamais un endpoint REST ni un client WebSocket directement. Une seule classe gère la connexion, la signature, l'environnement (production / demo) et la conversion vers les types unifiés Blackcube.
import { Blofin } from '@blackcube/blofin-sdk';
const dex = new Blofin({
network: 'testnet',
credentials: { apiKey: '…', secret: '…', passphrase: '…' },
});
// REST : requête → réponse
const candles = await dex.perp().getCandles({ name: 'BTC-USDT', interval: '1m', limit: 100 });
const order = await dex.perp().place({ name: 'BTC-USDT', side: 'buy', size: '0.0062' });
// WebSocket : abonnement → flux
const off = dex.ws().subscribeCandles({ name: 'BTC-USDT', interval: '1m' }, (candle) => {
console.log(candle.c);
});
off(); // se désabonne (ferme le socket s'il n'y a plus d'abonné)REST vs WebSocket — la seule distinction à connaître
- REST (
perp(),account()) : requête → réponse. Tuawaitun appel, tu reçois une valeur, terminé. - WebSocket (
ws()) : abonnement → flux. Tu passes un handler rappelé à chaque mise à jour, tant que tu n'as pas appelé la fonction de désabonnement renvoyée. Pas deconnect()/disconnect(): le socket s'ouvre au premiersubscribeet se ferme seul quand le dernier abonnement est retiré.
Tous les retours (REST comme WS) sont au format unifié (Candle, Order, OrderBook,
Position, UserTrade, Price, Balance…), identique entre les SDK Blackcube.
Construction
new Blofin(options?)| Option | Effet |
|---|---|
| network | 'mainnet' (défaut) ou 'testnet' — le vocabulaire commun à tous les SDK de la famille. BloFin appelle son testnet « demo trading » ; seul le host change, l'API et la signature sont identiques. |
| credentials | { apiKey, secret, passphrase }. Absent → seules les routes publiques répondent. |
| fetch / webSocket | Implémentations de substitution (tests, runtime particulier). |
| restUrl / wsPublicUrl / wsPrivateUrl | Forcent une URL, quel que soit l'environnement. |
Chaque instance porte sa propre configuration : plusieurs Blofin (production et demo, par
exemple) coexistent sans état global partagé.
Contrats et unités de base
BloFin exprime toutes ses tailles en contrats. Sur BTC-USDT, un contrat vaut 0,001 BTC :
la venue parle donc de « 6,2 » là où les autres exchanges parlent de « 0,0062 BTC ».
Le SDK traduit dans les deux sens. Tu donnes et tu reçois des unités de base, partout :
await dex.perp().place({ name: 'BTC-USDT', side: 'buy', size: '0.0062' }); // → 6,2 contrats
const [position] = await dex.perp().getPositions('BTC-USDT');
position.size; // '0.0062' — pas '6.2'Cela vaut pour les positions, les ordres, les exécutions, le carnet et les volumes de bougies.
La valeur d'un contrat reste disponible dans xtras.contractValue, et l'arithmétique est
décimale exacte — jamais flottante, parce que 0.1 × 0.1 vaut 0.010000000000000002 en
JavaScript et que c'est le pas réel de plusieurs marchés.
Deux conséquences :
- Une taille invalide est refusée localement, avant tout appel réseau, en nommant la taille valide la plus proche. BloFin, elle, répondrait « Parameter size error » sans dire quel paramètre.
- Les contrats
inversesont écartés degetPairs()(14 des 501). LeurcontractValueest libellé en devise de cotation, et les convertir exigerait le prix, absent du catalogue. Ils restent accessibles bruts pardex.native.perp().getInstruments().
Les scopes
dex.perp() — marché, trading et compte du produit
BloFin est perp-only : pas de scope spot(). Ce scope tient l'intégralité de IMarketData.
dex.perp().getPairs(); // catalogue unifié
dex.perp().getCandles({ name, interval });
dex.perp().getOrderBook({ name, limit });
dex.perp().getPrices(); // tout le marché
dex.perp().getFundingHistory({ name });
dex.perp().getPositions(name?);
dex.perp().getOpens(); // ordres au carnet
dex.perp().getHistory(); // ordres terminés
dex.perp().getUserTrades();
dex.perp().place({ name, side, size, price?, reduceOnly?, marginMode? });
dex.perp().cancel({ name, id });
dex.perp().updateLeverage({ name, leverage, marginMode? });Sans price, place passe un ordre au marché.
dex.account() — compte transverse
dex.account().getBalances(); // `total` porte l'equity (dépôt + PnL latent), pas le seul dépôtdex.ws() — temps réel
Deux sockets, ouvertes à la demande : la publique pour le marché, la privée (authentifiée) pour le compte.
dex.ws().subscribeCandles({ name, interval }, (candle) => {});
dex.ws().subscribeOrders((order) => {});
dex.ws().subscribePositions((positions) => {}); // la liste COMPLÈTE à chaque push
dex.ws().subscribeBalances((balances) => {});
dex.ws().disconnect(); // sortie franche, pour un process qui s'arrêtesubscribePositions livre l'état complet, pas un delta : un tableau vide signifie qu'il n'y a
plus de position. Reconnexion, heartbeat et ré-authentification sont automatiques.
Surface native — spécifique BloFin
dex.native.perp().getInstruments(instId?); // le catalogue brut, contrats inverse comprisExemples
// Le catalogue, trié par levier maximum
const pairs = await dex.perp().getPairs();
pairs.sort((a, b) => (b.maxLeverage ?? 0) - (a.maxLeverage ?? 0));
// Un aller-retour complet
await dex.perp().updateLeverage({ name: 'BTC-USDT', leverage: 10, marginMode: 'isolated' });
const opened = await dex.perp().place({
name: 'BTC-USDT', side: 'buy', size: '0.0062', marginMode: 'isolated',
});
const [position] = await dex.perp().getPositions('BTC-USDT');
await dex.perp().place({
name: 'BTC-USDT', side: 'sell', size: position.size, reduceOnly: true, marginMode: 'isolated',
});
// Suivre son compte en temps réel
dex.ws().subscribePositions((positions) => {
if (positions.length === 0) {
console.log('plus aucune position');
}
});Erreurs
Toutes les erreurs d'API lèvent un BlofinApiError, qui porte le code BloFin ("152409",
"152404"…) et son message.
BloFin répond HTTP 200 même en erreur : le statut ne dit rien, seul le code du corps
tranche. Le SDK s'en charge — y compris sur les routes d'ordre, qui portent un code par élément
et peuvent donc refuser un ordre dans une réponse dont l'enveloppe annonce un succès.
Environnements
| | REST | WebSocket |
|---|---|---|
| Production | openapi.blofin.com | /ws/public, /ws/private |
| Demo | demo-trading-openapi.blofin.com | idem |
Les mêmes clés API fonctionnent sur les deux.
License
BSD-3-Clause — Blackcube.
