Line data Source code
1 : import type { Tick } from './tick_feed.js';
2 : import { parseFeedNumber, WebSocketTickFeed, type FeedMessage, type WebSocketFeedOptions } from './websocket_feed.js';
3 :
4 : /// The public market data feed of Coinbase Exchange, its ticker channel needs no credentials
5 : export const COINBASE_FEED_URL = 'wss://ws-feed.exchange.coinbase.com';
6 :
7 : /// The pairs of the board
8 1 : export const COINBASE_PRODUCTS: readonly string[] = [
9 1 : 'BTC-USD', 'ETH-USD', 'SOL-USD', 'XRP-USD', 'DOGE-USD', 'ADA-USD', 'AVAX-USD', 'LINK-USD',
10 1 : 'DOT-USD', 'LTC-USD', 'BCH-USD', 'UNI-USD', 'AAVE-USD', 'ATOM-USD', 'XLM-USD', 'ETC-USD',
11 1 : 'FIL-USD', 'NEAR-USD', 'APT-USD', 'ARB-USD', 'OP-USD', 'SHIB-USD',
12 1 : ];
13 :
14 : /// The trade of a ticker message, null for other messages, e.g. the confirmation of the subscription
15 1 : export function parseCoinbaseMessage(data: string): Tick | null {
16 1 : let message: any;
17 1 : try {
18 1 : message = JSON.parse(data);
19 1 : } catch {
20 1 : return null;
21 1 : }
22 1 : if (message?.type !== 'ticker' || typeof message.product_id !== 'string') return null;
23 1 : const price = parseFeedNumber(message.price);
24 1 : const ts = typeof message.time === 'string' ? Date.parse(message.time) : NaN;
25 1 : if (price == null || !Number.isFinite(ts)) return null;
26 1 : return {
27 1 : symbol: message.product_id,
28 1 : ts,
29 1 : price,
30 1 : size: parseFeedNumber(message.last_size) ?? 0,
31 1 : bid: parseFeedNumber(message.best_bid),
32 1 : ask: parseFeedNumber(message.best_ask),
33 1 : };
34 1 : }
35 :
36 : /// The error of an error message of the feed, null for other messages
37 1 : export function parseCoinbaseError(data: string): string | null {
38 1 : try {
39 1 : const message = JSON.parse(data);
40 1 : if (message?.type !== 'error') return null;
41 1 : return [message.message, message.reason].filter(m => typeof m === 'string' && m.length > 0).join(': ') || 'error';
42 1 : } catch {
43 0 : return null;
44 0 : }
45 1 : }
46 :
47 : /// The options of the Coinbase feed
48 : export interface CoinbaseFeedOptions extends WebSocketFeedOptions {
49 : /// The url of the feed
50 : url?: string;
51 : /// The pairs to subscribe to
52 : products?: readonly string[];
53 : }
54 :
55 : /// The trades of crypto pairs on Coinbase, from its public ticker channel.
56 : /// A ticker message carries the trade together with the best bid and ask.
57 1 : export class CoinbaseFeed extends WebSocketTickFeed {
58 1 : constructor(options: CoinbaseFeedOptions = {}) {
59 1 : const products = options.products ?? COINBASE_PRODUCTS;
60 1 : super({
61 1 : url: options.url ?? COINBASE_FEED_URL,
62 1 : subscription: () => JSON.stringify({ type: 'subscribe', product_ids: products, channels: ['ticker'] }),
63 1 : read: (data: string): FeedMessage => {
64 1 : const tick = parseCoinbaseMessage(data);
65 1 : if (tick != null) return { tick };
66 1 : const error = parseCoinbaseError(data);
67 1 : return error == null ? null : { error };
68 1 : },
69 1 : }, options);
70 1 : }
71 1 : }
|