npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blackcube/blofin-sdk

v0.56.0

Published

TypeScript SDK for the BloFin exchange (perpetuals CEX): REST v1, WebSocket, HMAC-SHA256 signing

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-sdk

Node.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. Tu await un 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 de connect()/disconnect() : le socket s'ouvre au premier subscribe et 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 inverse sont écartés de getPairs() (14 des 501). Leur contractValue est libellé en devise de cotation, et les convertir exigerait le prix, absent du catalogue. Ils restent accessibles bruts par dex.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ôt

dex.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ête

subscribePositions 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 compris

Exemples

// 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.