@taphubhq/sdk-core 0.25.5 → 0.26.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 +351 -9
- package/dist/index.cjs +686 -31
- package/dist/index.d.mts +428 -12
- package/dist/index.d.ts +428 -12
- package/dist/index.js +674 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# @taphubhq/sdk-core
|
|
2
2
|
|
|
3
|
-
Framework-agnostic TypeScript SDK for the
|
|
3
|
+
Framework-agnostic TypeScript SDK for the TapHub platform. Wraps the grid-api and
|
|
4
|
+
user-service GraphQL endpoints plus the MQTT realtime streams behind one client.
|
|
5
|
+
|
|
6
|
+
For React bindings (hooks, providers, i18n), use
|
|
7
|
+
[`@taphubhq/sdk-react`](https://github.com/TaphubHQ/taphub-sdk/blob/main/packages/sdk-react/README.md), which builds on this package.
|
|
4
8
|
|
|
5
9
|
## Install
|
|
6
10
|
|
|
@@ -16,9 +20,11 @@ import { TaphubClient } from '@taphubhq/sdk-core';
|
|
|
16
20
|
const client = new TaphubClient({
|
|
17
21
|
agencyId: 'acme-corp',
|
|
18
22
|
// Shared API base. The SDK derives:
|
|
19
|
-
// - grid-api
|
|
23
|
+
// - grid-api → ${endpoint}/grid-api/grid-gql
|
|
20
24
|
// - user-service → ${endpoint}/taphub-user-service/th-user-gql
|
|
21
25
|
endpoint: 'https://builder.taptrading.net/api',
|
|
26
|
+
// Optional — omit to run without realtime (client.realtime stays undefined).
|
|
27
|
+
mqttEndpoint: 'wss://mqtt.taptrading.net/mqtt',
|
|
22
28
|
});
|
|
23
29
|
|
|
24
30
|
// Session-token login via user-service GraphQL (preferred).
|
|
@@ -27,16 +33,276 @@ await client.auth.loginWithSession(upstreamSessionToken);
|
|
|
27
33
|
const me = await client.user.me();
|
|
28
34
|
```
|
|
29
35
|
|
|
30
|
-
###
|
|
36
|
+
### Config
|
|
37
|
+
|
|
38
|
+
| Field | Required | Notes |
|
|
39
|
+
|----------------|----------|-------|
|
|
40
|
+
| `agencyId` | yes | Sent as `x-builder-code` on every request. Also namespaces token storage and scopes agency MQTT topics. |
|
|
41
|
+
| `endpoint` | yes | Absolute URL of the shared API base. Must parse as a URL — the constructor throws `TaphubValidationError` (`code: 'InvalidConfig'`) otherwise. |
|
|
42
|
+
| `mqttEndpoint` | no | When absent, `client.realtime` is `undefined`; every other module still works. |
|
|
43
|
+
| `mqttAuth` | no | `{ username, password }`, forwarded verbatim to the broker. For the TapHub/EMQX JWT-in-username setup, pass the session token as `username` and `''` as `password`. Read once when the connection opens — no refresh on reconnect. Ignored without `mqttEndpoint`. |
|
|
44
|
+
| `storage` | no | `TaphubStorageAdapter` for token persistence. Defaults to `localStorage` in the browser, in-memory elsewhere. |
|
|
45
|
+
| `fetch` | no | Custom `fetch` implementation (SSR, instrumentation, test doubles). |
|
|
46
|
+
|
|
47
|
+
### Naming: `pairId` vs `agencyPairId`
|
|
48
|
+
|
|
49
|
+
- **`pairId`** — the system-wide pair id, e.g. `grid-ETH-USD`. **Slash-free** by
|
|
50
|
+
design so it is safe inside MQTT topics, URLs, and Redis keys. This is what
|
|
51
|
+
every SDK method takes.
|
|
52
|
+
- **`agencyPairId`** — the agency-scoped composite `{agencyId}:{pairId}`, e.g.
|
|
53
|
+
`acme-corp:grid-ETH-USD`. The server composes it from your `x-builder-code` /
|
|
54
|
+
JWT; you only meet it as an MQTT `gameId` and on `PairInfo.agencyPairId`.
|
|
55
|
+
- **`ETH/USD`** is a *display* name (`Pair.pair`, `AgencyPairStats.pair`), never
|
|
56
|
+
an id.
|
|
57
|
+
|
|
58
|
+
The pre-`bid-260602-v2` names `game` / `gameId` / `gamePairId` are retired from
|
|
59
|
+
the public surface.
|
|
60
|
+
|
|
61
|
+
## Client surface
|
|
62
|
+
|
|
63
|
+
| Accessor | Module | Auth |
|
|
64
|
+
|---------------------|-------------------------|------|
|
|
65
|
+
| `client.auth` | `AuthModule` | — |
|
|
66
|
+
| `client.user` | `UserModule` | JWT |
|
|
67
|
+
| `client.pair` | `PairModule` | JWT (anon-tolerant) |
|
|
68
|
+
| `client.bid` | `BidModule` | JWT |
|
|
69
|
+
| `client.leaderboard`| `LeaderboardModule` | mixed (see below) |
|
|
70
|
+
| `client.agencyPairs`| `AgencyPairModule` | anon (`x-builder-code`) |
|
|
71
|
+
| `client.locale` | `LocaleModule` | anon |
|
|
72
|
+
| `client.realtime` | `RealtimeModule \| undefined` | broker creds |
|
|
73
|
+
| `client.network` | `NetworkQualityMonitor` | — |
|
|
74
|
+
|
|
75
|
+
Token helpers live on the client itself: `getToken()`, `setToken(token, { isDemo })`,
|
|
76
|
+
`isDemo()`.
|
|
77
|
+
|
|
78
|
+
> **Anon vs JWT reads.** `agencyPairs.list()`, `leaderboard.list()` and
|
|
79
|
+
> `leaderboard.rankBoard()` deliberately send **`x-builder-code` only, never the
|
|
80
|
+
> JWT**. A signed-in user can hold a JWT for a different agency than the one in
|
|
81
|
+
> context (a builder's demo user is always issued a `taptrading` JWT); attaching
|
|
82
|
+
> it would make the backend take its "JWT wins" branch and return the wrong
|
|
83
|
+
> agency's data.
|
|
84
|
+
|
|
85
|
+
## Auth
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
await client.auth.loginWithSession(sessionToken); // → LoginResult
|
|
89
|
+
await client.auth.logout();
|
|
90
|
+
```
|
|
31
91
|
|
|
32
|
-
| Method
|
|
33
|
-
|
|
92
|
+
| Method | Transport | Status |
|
|
93
|
+
|----------------------------------|----------------------|---------------|
|
|
34
94
|
| `loginWithSession(sessionToken)` | user-service GraphQL | **Preferred** |
|
|
35
|
-
| `createDemoUser(username?)`
|
|
36
|
-
| `loginWithGoogle(idToken)`
|
|
37
|
-
| `demoLogin(username?)`
|
|
95
|
+
| `createDemoUser(username?, opts?)` | grid-api GraphQL | Active |
|
|
96
|
+
| `loginWithGoogle(idToken)` | grid-api REST | `@deprecated` |
|
|
97
|
+
| `demoLogin(username?)` | grid-api REST | `@deprecated` |
|
|
98
|
+
|
|
99
|
+
REST-backed methods continue to work until the gateway retires the routes in a
|
|
100
|
+
follow-up change.
|
|
101
|
+
|
|
102
|
+
## User
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
const me = await client.user.me(); // Me
|
|
106
|
+
const pnl = await client.user.pnl({ period: 'weekly' }); // UserPnL — 'weekly' | '30d'
|
|
107
|
+
const wallets = await client.user.wallets(); // UserWallet[]
|
|
108
|
+
const usdc = await client.user.walletByCurrency('USDC.e'); // UserWallet
|
|
109
|
+
const enabled = await client.user.refreshCurrencies(); // Currency[]
|
|
110
|
+
client.user.clearCurrencies(); // drop the cached list
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`UserPnL` carries `gain` (= `totalPayout − totalWagered`), the wagered/payout
|
|
114
|
+
totals, bid/win counts, and both competition ranks (`pnlRank`, `volRank`; `0`
|
|
115
|
+
means unranked).
|
|
116
|
+
|
|
117
|
+
> `client.user.wallets()` returns `UserWallet` (`{ id, amount, currency, isEnable }`),
|
|
118
|
+
> which is **not** the `Wallet` returned by the auth module (`{ balance, isEnabled }`).
|
|
119
|
+
> The distinct export alias is deliberate — see the note in `src/index.ts`.
|
|
120
|
+
|
|
121
|
+
## Pairs
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const pairs = await client.pair.availableGamePairs({ gameplayId: 'taptrading' }); // PairInfo[]
|
|
125
|
+
const pair = await client.pair.get('grid-ETH-USD'); // Pair
|
|
126
|
+
const bars = await client.pair.chartHistory('grid-ETH-USD'); // Candle[]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
- `get(pairId)` throws `TaphubValidationError` with `code: 'GameNotFound'` when
|
|
130
|
+
the pair has no configured game for the agency in context. It also feeds the
|
|
131
|
+
clock-offset estimate (see [Clock sync](#clock-sync)).
|
|
132
|
+
- `chartHistory(pairId, limit?)` defaults to `DEFAULT_CHART_HISTORY_LIMIT` (600);
|
|
133
|
+
the server clamps any request to its cache size (1500).
|
|
134
|
+
- **`Candle.time` from GraphQL is Unix epoch SECONDS.** The MQTT
|
|
135
|
+
`MqttCandleEvent.time` is **milliseconds**. Do not multiply the MQTT value.
|
|
136
|
+
- `PairInfo.agencyPairId` is populated only for authenticated callers, and is the
|
|
137
|
+
value to feed into `realtime.subscribe()`.
|
|
138
|
+
- `Pair.config` carries `gridConfig` (`cellSizeTime`, `cellSizeValue`,
|
|
139
|
+
`candleSize`, `baseline`, `baselineTime`), `constraints`, bid amount bounds, and
|
|
140
|
+
the effective cancel policy (`bidCancelMinSeconds`, `bidCancelRefundRate`).
|
|
141
|
+
Treat `undefined` / `0` on the cancel fields as "use the platform default".
|
|
142
|
+
|
|
143
|
+
## Bids
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const bid = await client.bid.placeBid({
|
|
147
|
+
pairId: 'grid-ETH-USD',
|
|
148
|
+
walletId, // required — grid-api's wallet_id is ID!
|
|
149
|
+
time1, time2, // window, unix seconds
|
|
150
|
+
price1, price2, // decimal strings
|
|
151
|
+
coefficient: '2.35',
|
|
152
|
+
amount: '10',
|
|
153
|
+
slippage: 5, // tolerance percent
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const result = await client.bid.cancelBid(bid.id); // { bid, refundAmount, newBalance }
|
|
157
|
+
const mine = await client.bid.listBids({ statuses: ['pending'], limit: 20, pairId });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`placeBid` auto-stamps `ct` (raw client send time) and `clockOffset` at dispatch
|
|
161
|
+
so the server can record skew-corrected latency into `bid.meta`. Both are
|
|
162
|
+
untrusted tracing data — do not supply them yourself.
|
|
163
|
+
|
|
164
|
+
`listBids` takes `statuses: BidStatus[]` (include-semantics). The singular
|
|
165
|
+
`status` is `@deprecated` and folded into a one-element list; `statuses` wins when
|
|
166
|
+
both are present.
|
|
167
|
+
|
|
168
|
+
Status classifiers avoid string-literal typos:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { isPending, isTerminal, isWin, isLoss, isCancelled } from '@taphubhq/sdk-core';
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`Bid.refundRate` / `Bid.refundAmount` are present only on cancelled bids. Prefer
|
|
175
|
+
`refundAmount` for display — it is the exact credited value, so it needs no
|
|
176
|
+
recompute from a config rate. `Bid.meta.pairName` holds the display pair frozen
|
|
177
|
+
at placement; read it instead of mapping `pairId` yourself.
|
|
178
|
+
|
|
179
|
+
## Leaderboard & rank
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
const board = await client.leaderboard.rankBoard({
|
|
183
|
+
period: 'last_7d', // 'today' | 'yesterday' | 'last_7d' | 'last_30d' | 'all_time' | 'custom'
|
|
184
|
+
sort: 'pnl', // 'pnl' | 'vol'
|
|
185
|
+
sortDir: 'desc',
|
|
186
|
+
limit: 50, offset: 0,
|
|
187
|
+
}); // RankBoard — public, identical for every caller
|
|
188
|
+
|
|
189
|
+
const me = await client.leaderboard.myRank({ period: 'last_7d' }); // MyRank | null
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `period: 'custom'` requires an **hour-aligned** ISO-8601 UTC `range`.
|
|
193
|
+
- The board carries no per-user data by design (`bid-260606-leaderboard-split-my-rank`)
|
|
194
|
+
so it stays cacheable — `myRank()` is the per-user companion and does attach
|
|
195
|
+
the JWT. It returns `null` when nobody is signed in, and `MyRank.entry === null`
|
|
196
|
+
when the caller has no activity in the window.
|
|
197
|
+
- `leaderboard.list({ period, sortBy })` is the v1 query and is `@deprecated` on
|
|
198
|
+
both the SDK and the backend.
|
|
199
|
+
|
|
200
|
+
## Agency pairs (market list)
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
const rows = await client.agencyPairs.list({ filter: { status: 'active' } }); // AgencyPairStats[]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Returns the agency-in-context's pairs with `currentPrice`, `currentVol24h`
|
|
207
|
+
(sliding 24h bid-amount sum), `maxCoef`, `thumb`, and `ordering`. There is no
|
|
208
|
+
argument to target another agency by design. Seed your UI from this call, then
|
|
209
|
+
keep it live with `realtime.subscribeAgencyPairStats()` — the backend suppresses
|
|
210
|
+
unchanged ticks, so a subscriber may receive nothing until price or volume moves.
|
|
211
|
+
|
|
212
|
+
## Locale & error copy
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const res = await client.locale.get('vi', lastSeenVersion);
|
|
216
|
+
// → { lang, version, notModified, translations }
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Pass the last-seen `version` as `knownVersion` to get `{ notModified: true,
|
|
220
|
+
translations: null }` when the body is unchanged, and keep your prior state.
|
|
221
|
+
|
|
222
|
+
Error copy is a separate catalog keyed by the backend `extensions.code`:
|
|
38
223
|
|
|
39
|
-
|
|
224
|
+
```ts
|
|
225
|
+
await client.locale.loadErrorMessages(navigator.language); // never throws
|
|
226
|
+
|
|
227
|
+
// …later, inside a catch:
|
|
228
|
+
showToast(client.locale.errorMessage(e.code, 'Could not place your bid.'));
|
|
229
|
+
|
|
230
|
+
// with placeholders filled from the error's meta:
|
|
231
|
+
client.locale.errorMessage('Bid_CoefficientMismatch', 'Odds changed — try again.', {
|
|
232
|
+
serverCoef: e.serverCoef,
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- `loadErrorMessages(lang)` is background hydration: it **never throws and never
|
|
237
|
+
rejects**. Any failure leaves the last-known-good catalog in place — failing to
|
|
238
|
+
translate an error must not itself become an error. The tag is normalised
|
|
239
|
+
(region dropped, Chinese scripts kept apart), and concurrent calls for the same
|
|
240
|
+
language share one request.
|
|
241
|
+
- `errorMessage(code, fallback, meta?)` is **synchronous by design** — error copy
|
|
242
|
+
is read inside a `catch`, where React hooks are illegal. It returns `fallback`
|
|
243
|
+
whenever the catalog cannot produce a usable sentence, never an empty string and
|
|
244
|
+
never the raw code. `meta` fills `{name}` placeholders; unfilled placeholders are
|
|
245
|
+
stripped so raw braces never reach the user.
|
|
246
|
+
- `getErrorMessages(lang, knownVersion?)` is the throwing sibling, for callers who
|
|
247
|
+
want to see why a fetch failed.
|
|
248
|
+
- `refresh()` is an admin operation — the backend re-pulls the source sheet for
|
|
249
|
+
every supported language and requires an `X-API-Key` header.
|
|
250
|
+
|
|
251
|
+
## Realtime (MQTT)
|
|
252
|
+
|
|
253
|
+
`client.realtime` is `undefined` unless `mqttEndpoint` was configured. Channels
|
|
254
|
+
are refcounted: N `subscribe()` calls for the same key return the same channel and
|
|
255
|
+
need N matching `unsubscribe()` calls.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
const channel = client.realtime?.subscribe(agencyPairId, userId); // GameChannel
|
|
259
|
+
channel?.on('candle', (e) => drawCandle(e)); // MqttCandleEvent
|
|
260
|
+
channel?.on('bidAccepted', (e) => addLiveTrade(e)); // MqttBidAcceptedEvent
|
|
261
|
+
channel?.on('bidWon', (e) => celebrate(e));
|
|
262
|
+
channel?.on('bidLost', (e) => dim(e));
|
|
263
|
+
channel?.on('bidCancelled', (e) => remove(e.bidId));
|
|
264
|
+
channel?.on('balanceUpdate', (e) => setBalance(e.balance));
|
|
265
|
+
channel?.on('configUpdate', (e) => setBidBounds(e));
|
|
266
|
+
channel?.on('idealConfigUpdate', (e) => suggestCellSize(e));
|
|
267
|
+
channel?.on('error', (err) => report(err));
|
|
268
|
+
client.realtime?.unsubscribe(agencyPairId, userId);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Subscription | Key | Emits |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `subscribe(gameId, userId?)` | `agencyPairId` (+ user) | `GameChannel` — candle, bid lifecycle, balance, config |
|
|
274
|
+
| `subscribeWallet(userId)` | `userId` | `WalletChannel` — `walletBalanceUpdate` on **every** balance change, no game context |
|
|
275
|
+
| `subscribeUserBids(userId)` | `userId` | `UserBidsChannel` — `bidWon` / `bidLost` across **all** pairs, survives pair switches |
|
|
276
|
+
| `subscribeCandle(pairId, handler)` | `pairId` | raw `MqttCandleEvent`, market-wide public data |
|
|
277
|
+
| `subscribeAgencyPairStats(pairId, handler)` | `pairId` | `MqttAgencyPairStatsEvent` for the client's own agency |
|
|
278
|
+
|
|
279
|
+
Each has a matching `unsubscribe*`; `disconnect()` tears everything down.
|
|
280
|
+
|
|
281
|
+
Gotchas worth reading twice:
|
|
282
|
+
|
|
283
|
+
- **`MqttCandleEvent.time` is MILLISECONDS**, unlike the GraphQL `Candle.time`
|
|
284
|
+
(seconds). Compare against `CANDLE_EVENT.NEW` / `CANDLE_EVENT.UPDATE` rather
|
|
285
|
+
than bare string literals.
|
|
286
|
+
- `volatility` is the **fast** σ that prices coefficients; `slowVolatility` is the
|
|
287
|
+
slow σ used for grid geometry only.
|
|
288
|
+
- `MqttAcceptedBid` mirrors the wire shape, **not** the GraphQL `Bid`:
|
|
289
|
+
`coordinates` is nested, `coefficient` / `price1` / `price2` are numbers, and
|
|
290
|
+
`createdAt` is epoch milliseconds. `gameUuid` is `@deprecated` — read `pairId`.
|
|
291
|
+
- Won/lost payloads carry no pair, so the SDK recovers `pairId` from the result
|
|
292
|
+
topic. `pairIdFromBidResultTopic(topic)` is exported if you parse topics
|
|
293
|
+
yourself; it returns `undefined` on an unrecognised shape rather than throwing.
|
|
294
|
+
- `subscribeAgencyPairStats` throws `TaphubError` (`code: 'AgencyIdRequired'`) if
|
|
295
|
+
the client was built without an `agencyId`.
|
|
296
|
+
- `MqttWalletBalanceEvent.reason` is an open enum — unknown values are passed
|
|
297
|
+
through rather than dropped, so handle the default case.
|
|
298
|
+
|
|
299
|
+
## Clock sync
|
|
300
|
+
|
|
301
|
+
`client.pair.get()` brackets its request to measure RTT and records a one-shot
|
|
302
|
+
server-clock offset. `placeBid` then stamps that offset alongside the raw client
|
|
303
|
+
send time. This is best-effort: the estimate is set-once and finite-guarded, so it
|
|
304
|
+
never throws and never blocks the pair load. Nothing to configure — just be aware
|
|
305
|
+
that a client which never calls `pair.get()` reports an offset of `0`.
|
|
40
306
|
|
|
41
307
|
## Error Handling
|
|
42
308
|
|
|
@@ -81,6 +347,9 @@ The GraphQL transport classifies errors by the first entry's `extensions.code` a
|
|
|
81
347
|
|
|
82
348
|
`TaphubSlippageError extends TaphubValidationError`, so existing `instanceof TaphubValidationError` catch blocks keep working. Place the `TaphubSlippageError` check first when both branches exist.
|
|
83
349
|
|
|
350
|
+
To turn a thrown `code` into a user-facing sentence, use
|
|
351
|
+
[`client.locale.errorMessage()`](#locale--error-copy) rather than hardcoding copy.
|
|
352
|
+
|
|
84
353
|
### Slippage error fallback
|
|
85
354
|
|
|
86
355
|
`TaphubSlippageError` is only thrown when `extensions.meta.clientCoef`, `extensions.meta.serverCoef`, and `extensions.meta.slippage` are all finite numbers. The `slippage` field is the client-submitted tolerance percent (e.g. `5` for 5%). If `meta` is missing or malformed, the SDK falls back to a generic `TaphubValidationError` with `code === 'Bid_CoefficientMismatch'`. Consumers SHOULD treat the generic fallback as the same user-facing rejection but without typed coefficient access.
|
|
@@ -114,6 +383,79 @@ The monitor is always present, even when `mqttEndpoint` is not configured. In SS
|
|
|
114
383
|
- Connection samples from `navigator.connection` are used only as a cold-start fallback; once 3 real samples accumulate they no longer contribute to the EMA.
|
|
115
384
|
- MQTT QoS1 `puback` timing is not yet plumbed; v1 derives MQTT signal from `connect` / `reconnect` / `disconnect` events only.
|
|
116
385
|
|
|
386
|
+
## Events
|
|
387
|
+
|
|
388
|
+
A small client-wide bus for cross-cutting signals:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
import type { TaphubEventMap } from '@taphubhq/sdk-core';
|
|
392
|
+
// 'auth-expired' → void
|
|
393
|
+
// 'network:change' → { previous: NetworkQuality; current: NetworkQuality }
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Storage
|
|
397
|
+
|
|
398
|
+
Token persistence goes through a 3-method adapter, namespaced per agency
|
|
399
|
+
(`taphub:{agencyId}:token`):
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
import { TaphubStorage, autoDetectStorage } from '@taphubhq/sdk-core';
|
|
403
|
+
|
|
404
|
+
new TaphubClient({ ..., storage: TaphubStorage.memory() }); // SSR / tests
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
`autoDetectStorage()` is the default: `localStorage` in the browser, in-memory
|
|
408
|
+
elsewhere. Implement `TaphubStorageAdapter` (`get` / `set` / `remove`) for
|
|
409
|
+
anything else.
|
|
410
|
+
|
|
411
|
+
## Coefficient math
|
|
412
|
+
|
|
413
|
+
`placeBid` takes a `coefficient`, so a headless client has to compute one before it
|
|
414
|
+
can bid. `calculateCoefficientWrapper` is the model the server validates against —
|
|
415
|
+
the same first-passage computation the grid chart renders with, and the same one
|
|
416
|
+
grid-api runs at placement:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import {
|
|
420
|
+
calculateCoefficientWrapper,
|
|
421
|
+
type CoefficientInput,
|
|
422
|
+
} from '@taphubhq/sdk-core';
|
|
423
|
+
|
|
424
|
+
const coefficient = calculateCoefficientWrapper({
|
|
425
|
+
time1, time2, price1, price2, // the cell you are bidding on
|
|
426
|
+
candleTime, candleClose, volatility, // the live candle
|
|
427
|
+
coefMults, minCoef, // from pair.config
|
|
428
|
+
cellSizeTime, candleSize, // from pair.config
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
A return of `Number.POSITIVE_INFINITY` means the cell cannot be priced right now —
|
|
433
|
+
do not pass it to `placeBid`.
|
|
434
|
+
|
|
435
|
+
The server stays authoritative: a value that disagrees beyond your `slippage`
|
|
436
|
+
tolerance is rejected with `Bid_CoefficientMismatch` (see
|
|
437
|
+
[Slippage error fallback](#slippage-error-fallback)).
|
|
438
|
+
|
|
439
|
+
**Candle-interval variants.** `candleSize` is part of `CoefficientInput` but does not
|
|
440
|
+
enter the coefficient: the cell width is measured in bars (`cellSizeTime`), and
|
|
441
|
+
`calculateProbHit` already prices the real seconds window, so dividing by
|
|
442
|
+
`candleSize` a second time would double-penalise the 1m/5m variants by roughly
|
|
443
|
+
60×/300× (`bid-260703`). Pass the value from `pair.config` anyway —
|
|
444
|
+
`computeBaseline` uses it.
|
|
445
|
+
|
|
446
|
+
### Deprecated helpers
|
|
447
|
+
|
|
448
|
+
`calculateProbWin` and `calculateProbWin_v2` are **deprecated**. They compute a
|
|
449
|
+
one-sided exceedance probability and ignore `price1`, so they do not match the model
|
|
450
|
+
placement validates against. A coefficient derived from them can pass the
|
|
451
|
+
server's one-directional slippage check and lock a lower payout than the cell is
|
|
452
|
+
worth. Use `calculateCoefficientWrapper`. They remain exported, unchanged, for
|
|
453
|
+
existing callers.
|
|
454
|
+
|
|
455
|
+
The lower-level pieces — `calculateProbHit`, `roundCoefToSignificantDigits`,
|
|
456
|
+
`computeBaseline`, `normalCDF`, `normalPDF`, `errorFunction`, `adaptiveSimpson` —
|
|
457
|
+
are also exported for consumers that need to build on them directly.
|
|
458
|
+
|
|
117
459
|
## License
|
|
118
460
|
|
|
119
461
|
MIT
|