@getdashfy/ext-market-live 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,310 @@
1
+ # `@getdashfy/ext-market-live`
2
+
3
+ ![Full README Row](https://shieldcn.dev/group/npm/@getdashfy/ext-market-live+github/stars/dashfy/dashfy-ext-market-live+github/ci/dashfy/dashfy-ext-market-live+github/license/dashfy/dashfy-ext-market-live.svg?variant=branded&size=xs)
4
+
5
+ [![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/dashfy?referralCode=INMsTa&utm_medium=integration&utm_source=template&utm_campaign=generic)
6
+
7
+ > Market Live extension for [Dashfy](https://github.com/dashfy/dashfy) - Real-time prices from the Pyth Network oracle.
8
+
9
+ This extension provides widgets to display live prices for crypto, equities, commodities, forex, and rates, streamed straight from the [Pyth Network](https://pyth.network) oracle on Solana.
10
+
11
+ ![Dashfy dashboard with Market Live extension widgets](https://raw.githubusercontent.com/dashfy/dashfy-ext-market-live/refs/heads/main/preview/dashfy-ext-market-live.png)
12
+
13
+ ## Features
14
+
15
+ - **📈 Live prices**: Sub-second price updates streamed from the Pyth oracle
16
+ - **🌍 Five asset classes**: Crypto, US equities, commodities, forex, and rates
17
+ - **📊 Real-time chart**: Rolling price chart with 1m/5m/15m windows
18
+ - **📋 Multi-asset table**: Track many feeds side by side in one widget
19
+ - **🔌 No API key**: Connects directly to Pythnet, so there is nothing to configure
20
+ - **⚡ Shared connection**: All widgets reuse a single WebSocket, however many you render
21
+ - **🎨 Theme support**: Works with all Dashfy themes (light/dark mode)
22
+
23
+ ## Installation
24
+
25
+ Install with your favorite package manager:
26
+
27
+ #### `npm`
28
+
29
+ ```bash
30
+ npm install @getdashfy/ext-market-live
31
+ ```
32
+
33
+ #### `pnpm`
34
+
35
+ ```bash
36
+ pnpm add @getdashfy/ext-market-live
37
+ ```
38
+
39
+ #### `yarn`
40
+
41
+ ```bash
42
+ yarn add @getdashfy/ext-market-live
43
+ ```
44
+
45
+ #### `bun`
46
+
47
+ ```bash
48
+ bun add @getdashfy/ext-market-live
49
+ ```
50
+
51
+ ## Quick start
52
+
53
+ Unlike most Dashfy extensions, Market Live needs no server setup, API client, or token. The widgets open their own connection to Pythnet from the browser, so there are only two steps.
54
+
55
+ ### 1. Client setup
56
+
57
+ Register Market Live widgets in your React application (`App.tsx`):
58
+
59
+ ```tsx
60
+ import { WidgetRegistry } from '@getdashfy/ui'
61
+ import { PriceLive, TableLive } from '@getdashfy/ext-market-live'
62
+
63
+ // Register Market Live extension
64
+ WidgetRegistry.addExtension('market-live', {
65
+ PriceLive,
66
+ TableLive,
67
+ })
68
+ ```
69
+
70
+ ### 2. Dashboard configuration
71
+
72
+ Add Market Live widgets to your dashboard configuration (`dashfy.config.yml`):
73
+
74
+ ```yaml
75
+ dashboards:
76
+ - title: Markets Dashboard
77
+ columns: 3
78
+ rows: 2
79
+ widgets:
80
+ - extension: market-live
81
+ widget: PriceLive
82
+ feedId: crypto.BTC_USD
83
+ x: 0
84
+ y: 0
85
+ columns: 2
86
+ rows: 1
87
+
88
+ - extension: market-live
89
+ widget: TableLive
90
+ subject: Watchlist
91
+ x: 2
92
+ y: 0
93
+ columns: 1
94
+ rows: 2
95
+ ```
96
+
97
+ ## Feed identifiers
98
+
99
+ Every widget takes Pyth feeds by identifier, and `resolvePythFeedId` accepts three forms:
100
+
101
+ | Form | Example | Notes |
102
+ | -------------- | ---------------------------------------------- | -------------------------------------- |
103
+ | `category.key` | `equities.US_AAPL_USD` | Preferred, and unambiguous |
104
+ | Key only | `BTC_USD` | Category defaults to `crypto` |
105
+ | Base58 address | `GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU` | Any Pyth price account, even a new one |
106
+
107
+ The five categories are `crypto`, `equities`, `commodities`, `forex`, and `rates`, covering roughly 2,900 feeds in total. Keys come from the Pyth symbol with the category prefix stripped and separators replaced by underscores, so `Crypto.BTC/USD` becomes `crypto.BTC_USD` and `Equity.US.AAPL/USD` becomes `equities.US_AAPL_USD`.
108
+
109
+ Browse the full list at [pyth.network/developers/price-feed-ids](https://pyth.network/developers/price-feed-ids), or import a catalog and inspect its keys:
110
+
111
+ ```ts
112
+ import { PYTH_CRYPTO_FEEDS, PYTH_EQUITIES_FEEDS } from '@getdashfy/ext-market-live'
113
+
114
+ Object.keys(PYTH_CRYPTO_FEEDS) // ['1INCH_USD', 'AAVE_USD', 'ADA_USD', ...]
115
+ ```
116
+
117
+ An unknown category or key throws, so a typo surfaces as a widget error rather than a silently blank price.
118
+
119
+ ## Available widgets
120
+
121
+ ### `PriceLive`
122
+
123
+ Display the live price of a single feed, with an optional rolling chart and trend arrow.
124
+
125
+ <img src="https://raw.githubusercontent.com/dashfy/dashfy-ext-market-live/refs/heads/main/preview/market-live.PriceLive.png" alt="PriceLive widget preview" width="640" />
126
+
127
+ **Parameters:**
128
+
129
+ | Parameter | Type | Required | Default | Description |
130
+ | ------------- | ------- | -------- | ----------------- | -------------------------------------------- |
131
+ | `title` | string | no | "Price Live" | Custom widget title |
132
+ | `subject` | string | no | derived from feed | Custom widget subject |
133
+ | `locale` | string | no | "en-US" | Locale for price formatting |
134
+ | `feedId` | string | no | `crypto.BTC_USD` | Pyth feed identifier |
135
+ | `showChart` | boolean | no | `true` | Show the rolling price chart below the price |
136
+ | `showTrend` | boolean | no | `true` | Show the trend arrow next to the price |
137
+ | `showWindows` | boolean | no | `true` | Show the 1m/5m/15m time window buttons |
138
+
139
+ When `subject` is omitted, the label is derived from `feedId` (`crypto.BTC_USD` becomes `BTC/USD`). A raw base58 address has no derivable label, so pass `subject` explicitly in that case.
140
+
141
+ **Example:**
142
+
143
+ ```yaml
144
+ - extension: market-live
145
+ widget: PriceLive
146
+ feedId: crypto.BTC_USD
147
+ columns: 2
148
+ rows: 1
149
+ ```
150
+
151
+ **Example (price only, no chart):**
152
+
153
+ ```yaml
154
+ - extension: market-live
155
+ widget: PriceLive
156
+ subject: Gold
157
+ feedId: commodities.XAU_USD
158
+ showChart: false
159
+ columns: 1
160
+ rows: 1
161
+ ```
162
+
163
+ ### `TableLive`
164
+
165
+ Display a table of live prices for many feeds at once, each row showing symbol, price, and trend.
166
+
167
+ <img src="https://raw.githubusercontent.com/dashfy/dashfy-ext-market-live/refs/heads/main/preview/market-live.TableLive.png" alt="TableLive widget preview" width="640" />
168
+
169
+ **Parameters:**
170
+
171
+ | Parameter | Type | Required | Default | Description |
172
+ | ----------- | ----------------- | -------- | -------------- | ---------------------------------------- |
173
+ | `title` | string | no | "Markets Live" | Custom widget title |
174
+ | `subject` | string | no | - | Custom widget subject |
175
+ | `locale` | string | no | "en-US" | Locale for price formatting |
176
+ | `feeds` | `TableLiveFeed[]` | no | see below | Feeds to display, one per row |
177
+ | `showTrend` | boolean | no | `true` | Show the trend arrow in the Price column |
178
+
179
+ Each entry in `feeds` is an object with an `id` (any [feed identifier](#feed-identifiers)) and an optional `label`. When `label` is omitted, the row falls back to the derived pair, then to the raw `id`.
180
+
181
+ The default `feeds` list is a broad market snapshot: Bitcoin, Ethereum, Solana, gold, silver, and the seven largest US tech equities.
182
+
183
+ **Example:**
184
+
185
+ ```yaml
186
+ - extension: market-live
187
+ widget: TableLive
188
+ subject: Crypto Watchlist
189
+ feeds:
190
+ - id: crypto.BTC_USD
191
+ label: Bitcoin
192
+ - id: crypto.ETH_USD
193
+ label: Ethereum
194
+ - id: crypto.SOL_USD
195
+ label: Solana
196
+ columns: 1
197
+ rows: 2
198
+ ```
199
+
200
+ **Example (mixed asset classes):**
201
+
202
+ ```yaml
203
+ - extension: market-live
204
+ widget: TableLive
205
+ subject: Macro
206
+ feeds:
207
+ - id: commodities.XAU_USD
208
+ label: Gold
209
+ - id: forex.EUR_USD
210
+ label: Euro
211
+ - id: equities.US_AAPL_USD
212
+ label: Apple
213
+ columns: 1
214
+ rows: 2
215
+ ```
216
+
217
+ Rows render as soon as any feed reports, so a slow feed shows a placeholder instead of blocking the whole table.
218
+
219
+ ## Programmatic API
220
+
221
+ The package also exports the Pyth plumbing behind the widgets, for building your own custom widgets on the same shared connection.
222
+
223
+ | Export | Description |
224
+ | ----------------------------------------------- | ------------------------------------------------------------------------ |
225
+ | `subscribeToPrice(feedPublicKey, callback)` | Subscribe to a feed; the callback receives `(price, status)` per update |
226
+ | `unsubscribeFromPrice(feedPublicKey, callback)` | Remove a callback, tearing down the connection once the last one is gone |
227
+ | `getPythConnection()` | The shared `PythConnection`, or `null` before the first subscription |
228
+ | `resolvePythFeedId(value)` | Resolve any feed identifier to a Solana `PublicKey` |
229
+ | `getPythFeedDisplayPair(feedId)` | Human-readable pair for a feed (`crypto.BTC_USD` becomes `BTC/USD`) |
230
+ | `isPythErrorStatus(status)` | Whether a status means the price is unreliable |
231
+ | `PYTH_ERROR_STATUSES` | The statuses treated as errors |
232
+ | `PYTH_CRYPTO_FEEDS` and the four siblings | Feed catalogs keyed by symbol, one per category |
233
+
234
+ ```tsx
235
+ import {
236
+ PYTH_CRYPTO_FEEDS,
237
+ subscribeToPrice,
238
+ unsubscribeFromPrice,
239
+ } from '@getdashfy/ext-market-live'
240
+ import * as React from 'react'
241
+
242
+ const MyPriceWidget = () => {
243
+ const [price, setPrice] = React.useState(0)
244
+ const feedId = PYTH_CRYPTO_FEEDS.BTC_USD
245
+
246
+ React.useEffect(() => {
247
+ const handleUpdate = (newPrice: number) => setPrice(newPrice)
248
+
249
+ subscribeToPrice(feedId, handleUpdate)
250
+
251
+ return () => unsubscribeFromPrice(feedId, handleUpdate)
252
+ }, [feedId])
253
+
254
+ return <div>${price}</div>
255
+ }
256
+ ```
257
+
258
+ All subscribers share one Solana RPC connection and one `PythConnection`. Connection rebuilds are debounced, so subscribing to many feeds in a loop triggers a single rebuild rather than one per feed.
259
+
260
+ ## Price statuses
261
+
262
+ Widgets surface the Pyth price status rather than guessing at stale data:
263
+
264
+ - `Live` — price and confidence are available, so the value is rendered.
265
+ - `Connecting` — the initial state before the first update arrives; widgets show a loader.
266
+ - `Halted`, `Auction`, `Ignored`, `Unknown` — the price is unreliable and widgets show an error state.
267
+
268
+ Equity feeds are only `Live` during US market hours; outside them they report a non-trading status, which is expected rather than a failure.
269
+
270
+ ## Regenerating feed catalogs
271
+
272
+ The catalogs under `src/lib/pyth-feeds/` are generated from the live Pythnet program accounts and are checked in, so no network access is needed at build or run time. To refresh them after Pyth adds feeds:
273
+
274
+ ```bash
275
+ pnpm generate:feeds
276
+ ```
277
+
278
+ The script rewrites one file per category plus an index barrel. The files carry an `AUTO-GENERATED - DO NOT EDIT` header; edit the generator at `src/scripts/generate-pyth-feeds.ts` instead.
279
+
280
+ ## Troubleshooting
281
+
282
+ ### Prices stay on "Connecting"
283
+
284
+ **Solution:** The widgets reach Pythnet over a WebSocket from the browser. Check that the network or a corporate proxy is not blocking the Pythnet RPC endpoint, and that no ad blocker is interfering with the connection.
285
+
286
+ ### "Unknown feed" or "Unknown feed category" error
287
+
288
+ **Solution:** Check the identifier against the [feed identifier](#feed-identifiers) forms. Categories are `crypto`, `equities`, `commodities`, `forex`, and `rates`, and keys use underscores (`BTC_USD`, not `BTC/USD`).
289
+
290
+ ### An equity price shows an error state
291
+
292
+ **Solution:** Equity feeds only trade during US market hours and report `Halted` or `Unknown` outside them. Use a crypto feed to confirm the connection itself is healthy, since crypto trades continuously.
293
+
294
+ ### No subject label on a widget
295
+
296
+ **Solution:** A raw base58 feed address has no reverse lookup, so no label can be derived. Pass `subject` explicitly, or use the `category.key` form instead.
297
+
298
+ ## Contributing
299
+
300
+ Contributions are welcome. For issues and pull requests related to the extension, use the [dashfy/dashfy-ext-market-live](https://github.com/dashfy/dashfy-ext-market-live) repository. Framework contributions belong in [dashfy/dashfy](https://github.com/dashfy/dashfy).
301
+
302
+ ## Community
303
+
304
+ Join the community on [Dashfy's Discord server](https://dashfy.dev/discord) to discuss the project, ask questions, or get help.
305
+
306
+ Join the conversation on X (Twitter) and follow [@dashfydev](https://x.com/dashfydev) for updates and announcements.
307
+
308
+ ## License
309
+
310
+ This project is licensed under the AGPL-3.0 License - see the [LICENSE](./LICENSE) file for details.