@taphubhq/sdk-core 0.26.1 → 0.27.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 CHANGED
@@ -12,6 +12,18 @@ For React bindings (hooks, providers, i18n), use
12
12
  pnpm add @taphubhq/sdk-core
13
13
  ```
14
14
 
15
+ ### Server compatibility
16
+
17
+ The SDK's queries select the fields they need unconditionally, so a release that
18
+ adds a field also raises the minimum grid-api it runs against. Check this before
19
+ upgrading in an environment you do not deploy the server to.
20
+
21
+ | sdk-core | Needs grid-api serving | Or else |
22
+ | --- | --- | --- |
23
+ | 0.27.0+ | `Bid.cellCandle { o h l c final touchSec touchPrice }` | `myBids` fails GraphQL validation and returns **no bids** |
24
+
25
+ Deploy grid-api before the client, on every environment.
26
+
15
27
  ## Minimum Integration
16
28
 
17
29
  ```ts
package/dist/index.cjs CHANGED
@@ -661,7 +661,22 @@ function normaliseBid(node) {
661
661
  refundAmount: node.refund_amount ?? null,
662
662
  slippage: node.slippage,
663
663
  createdAt: node.created_at || null,
664
- meta: parseMeta(node.meta)
664
+ meta: parseMeta(node.meta),
665
+ // Absent and explicit null both mean "no candle" — an older server, an unselected
666
+ // field, a pending bid and purged ticks must all read the same to a consumer.
667
+ // `final` null (nullable Boolean) collapses to absent = settled, so only a real
668
+ // `false` puts the row in the in-progress presentation.
669
+ cellCandle: node.cellCandle ? {
670
+ o: node.cellCandle.o,
671
+ h: node.cellCandle.h,
672
+ l: node.cellCandle.l,
673
+ c: node.cellCandle.c,
674
+ ...typeof node.cellCandle.final === "boolean" ? { final: node.cellCandle.final } : {},
675
+ // Both legs or neither — see BidCellCandle.touchSec. Nulls (a non-win,
676
+ // or an older server) collapse to absent so a consumer only has to
677
+ // check one of them.
678
+ ...typeof node.cellCandle.touchSec === "number" && typeof node.cellCandle.touchPrice === "string" ? { touchSec: node.cellCandle.touchSec, touchPrice: node.cellCandle.touchPrice } : {}
679
+ } : null
665
680
  };
666
681
  }
667
682
  function normaliseBids(list) {
@@ -682,6 +697,7 @@ var PLACE_BID_MUTATION = `mutation PlaceBid($input: PlaceBidInput!) {
682
697
  var MY_BIDS_QUERY = `query MyBids($statuses: [BidStatus!], $limit: Int, $offset: Int, $pairId: ID) {
683
698
  myBids(statuses: $statuses, limit: $limit, offset: $offset, pairId: $pairId) {
684
699
  id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout refund_rate refund_amount slippage created_at meta
700
+ cellCandle { o h l c final touchSec touchPrice }
685
701
  }
686
702
  }`;
687
703
  var CANCEL_BID_MUTATION = `mutation CancelBid($input: CancelBidInput!) {
@@ -1849,6 +1865,23 @@ function mapWireBidResult(raw, topicPairId) {
1849
1865
  userId: p.user_id
1850
1866
  };
1851
1867
  if (typeof p.balance === "string") data.balance = p.balance;
1868
+ const candle = p.cellCandle;
1869
+ if (candle && typeof candle.o === "string" && typeof candle.h === "string" && typeof candle.l === "string" && typeof candle.c === "string") {
1870
+ data.cellCandle = {
1871
+ o: candle.o,
1872
+ h: candle.h,
1873
+ l: candle.l,
1874
+ c: candle.c,
1875
+ final: candle.final !== "false"
1876
+ };
1877
+ if (typeof candle.touchSec === "string" && typeof candle.touchPrice === "string") {
1878
+ const touchSec = Number.parseInt(candle.touchSec, 10);
1879
+ if (Number.isFinite(touchSec)) {
1880
+ data.cellCandle.touchSec = touchSec;
1881
+ data.cellCandle.touchPrice = candle.touchPrice;
1882
+ }
1883
+ }
1884
+ }
1852
1885
  if (topicPairId !== void 0) data.pairId = topicPairId;
1853
1886
  return { event: "bidWon", data };
1854
1887
  }
@@ -1857,6 +1890,32 @@ function mapWireBidResult(raw, topicPairId) {
1857
1890
  if (topicPairId !== void 0) data.pairId = topicPairId;
1858
1891
  return { event: "bidLost", data };
1859
1892
  }
1893
+ case "cell_candle_final": {
1894
+ const candle = p.cellCandle;
1895
+ if (!candle || typeof candle.o !== "string" || typeof candle.h !== "string" || typeof candle.l !== "string" || typeof candle.c !== "string") {
1896
+ return null;
1897
+ }
1898
+ const data = {
1899
+ bidId: p.bidId,
1900
+ userId: p.user_id,
1901
+ cellCandle: {
1902
+ o: candle.o,
1903
+ h: candle.h,
1904
+ l: candle.l,
1905
+ c: candle.c,
1906
+ final: candle.final !== "false"
1907
+ }
1908
+ };
1909
+ if (typeof candle.touchSec === "string" && typeof candle.touchPrice === "string") {
1910
+ const touchSec = Number.parseInt(candle.touchSec, 10);
1911
+ if (Number.isFinite(touchSec)) {
1912
+ data.cellCandle.touchSec = touchSec;
1913
+ data.cellCandle.touchPrice = candle.touchPrice;
1914
+ }
1915
+ }
1916
+ if (topicPairId !== void 0) data.pairId = topicPairId;
1917
+ return { event: "cellCandleFinal", data };
1918
+ }
1860
1919
  default:
1861
1920
  console.warn(`Unknown bid_result type "${String(p.type)}"`);
1862
1921
  return null;
@@ -1974,6 +2033,7 @@ var RealtimeModule = class extends import_eventemitter35.default {
1974
2033
  else if (mapped.event === "bidAccepted") channel.emit("bidAccepted", mapped.data);
1975
2034
  else if (mapped.event === "bidWon") channel.emit("bidWon", mapped.data);
1976
2035
  else if (mapped.event === "bidLost") channel.emit("bidLost", mapped.data);
2036
+ else if (mapped.event === "cellCandleFinal") channel.emit("cellCandleFinal", mapped.data);
1977
2037
  else if (mapped.event === "balanceUpdate") channel.emit("balanceUpdate", mapped.data);
1978
2038
  else if (mapped.event === "configUpdate") channel.emit("configUpdate", mapped.data);
1979
2039
  else if (mapped.event === "idealConfigUpdate")
@@ -2106,6 +2166,7 @@ var RealtimeModule = class extends import_eventemitter35.default {
2106
2166
  if (!mapped) return;
2107
2167
  if (mapped.event === "bidWon") channel.emit("bidWon", mapped.data);
2108
2168
  else if (mapped.event === "bidLost") channel.emit("bidLost", mapped.data);
2169
+ else if (mapped.event === "cellCandleFinal") channel.emit("cellCandleFinal", mapped.data);
2109
2170
  };
2110
2171
  const onError = (err) => {
2111
2172
  channel.emit("error", err);
package/dist/index.d.mts CHANGED
@@ -326,6 +326,39 @@ interface BidMeta {
326
326
  /** Display pair, e.g. "ETH/USD". */
327
327
  pairName: string;
328
328
  }
329
+ /**
330
+ * The OHLC of a bid's cell window — what price did inside [time1, time2] on the bid's
331
+ * own pair, reduced from every stored tick covering it.
332
+ *
333
+ * Values are Decimal strings, not numbers: the archive stores 8 decimal places and
334
+ * parsing to a JS number here would round them before the UI ever sees them.
335
+ */
336
+ interface BidCellCandle {
337
+ o: string;
338
+ h: string;
339
+ l: string;
340
+ c: string;
341
+ /**
342
+ * Whether the candle had CLOSED when it settled the bid.
343
+ *
344
+ * `false` = the values can still move: a 1m/5m win settles the instant the
345
+ * band is touched (the verdict is permanent) but the candle's O/C and extremes
346
+ * keep moving until it closes — delivered live over MQTT, and by `myBids` when
347
+ * the touching candle has not closed at read time. The UI shows such a value
348
+ * in the same not-yet-settled language the pending row uses. Absent (a server
349
+ * that predates the flag) means settled.
350
+ */
351
+ final?: boolean;
352
+ /**
353
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
354
+ * entered the bid's band, and the price inside the band it entered at — what
355
+ * the popover shows as "reached <price> at <time>". Both present or both
356
+ * absent: a bid that did not win has no single moment that won it, and a
357
+ * server predating the field sends neither.
358
+ */
359
+ touchSec?: number;
360
+ touchPrice?: string;
361
+ }
329
362
  interface Bid {
330
363
  id: string;
331
364
  userId: string;
@@ -363,6 +396,16 @@ interface Bid {
363
396
  * display pair instead of mapping `pairId` yourself.
364
397
  */
365
398
  meta?: BidMeta | null;
399
+ /**
400
+ * What price did inside this bid's cell window, served with the bid itself so a
401
+ * consumer never has to fetch it separately (openspec ux-260819-bid-detail-popover).
402
+ *
403
+ * Null in three cases a consumer should treat alike: the bid is still pending, its
404
+ * ticks are no longer stored, or the caller's query did not select the field. A
405
+ * cancelled bid DOES carry one — its window still elapsed — and the server attaches
406
+ * no verdict about what the outcome would have been.
407
+ */
408
+ cellCandle?: BidCellCandle | null;
366
409
  }
367
410
  interface CancelBidResult {
368
411
  bid: Bid;
@@ -1166,6 +1209,37 @@ interface MqttBidWonEvent {
1166
1209
  payout: string;
1167
1210
  userId: string;
1168
1211
  balance?: string;
1212
+ /**
1213
+ * OHLC of the candle whose band touch declared this win — the bid's
1214
+ * `cellCandle`, shipped with the result so the client never has to refetch the
1215
+ * bid list to learn it (openspec ux-260819, revised D2e).
1216
+ *
1217
+ * Sent on live wins and on D10 recovery alike (recovery hands over the stored
1218
+ * candle whose touch it found). Absent only on any grid-api older than that
1219
+ * change.
1220
+ */
1221
+ cellCandle?: {
1222
+ o: string;
1223
+ h: string;
1224
+ l: string;
1225
+ c: string;
1226
+ /**
1227
+ * Whether that candle had CLOSED when it settled the bid. A 1m/5m win settles
1228
+ * the instant the band is touched — safe, because a touch is permanent — but
1229
+ * the candle's O/C and extremes can still move until it closes. `false` means
1230
+ * the client must present the value as not-yet-settled rather than final.
1231
+ */
1232
+ final: boolean;
1233
+ /**
1234
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
1235
+ * entered the bid's band, and the price inside the band it entered at. The
1236
+ * engine's predicate is a range overlap, so this is neither the candle's
1237
+ * close nor a raw extreme. Both present or both absent — a server that
1238
+ * predates the field sends neither.
1239
+ */
1240
+ touchSec?: number;
1241
+ touchPrice?: string;
1242
+ };
1169
1243
  /**
1170
1244
  * Pair the won bid belongs to, e.g. "grid-ETH-USD". Derived from the result
1171
1245
  * topic (`game/{agencyId}:{pairId}/user/.../bid_result`) since the won payload
@@ -1175,6 +1249,38 @@ interface MqttBidWonEvent {
1175
1249
  */
1176
1250
  pairId?: string;
1177
1251
  }
1252
+ /**
1253
+ * Follow-up patch grid-api publishes when the window of a building-candle win
1254
+ * closes (wire type `cell_candle_final`): the CLOSED o/h/l/c of the candle the
1255
+ * bid settled on, replacing the in-progress values the `bidWon` event carried
1256
+ * with `cellCandle.final: false` (openspec ux-260819, design D2g).
1257
+ *
1258
+ * A patch, not a result: consumers must only update the bid's stored candle —
1259
+ * never replay win side effects (sound, payout, balance).
1260
+ */
1261
+ interface MqttCellCandleFinalEvent {
1262
+ bidId: string;
1263
+ userId: string;
1264
+ cellCandle: {
1265
+ o: string;
1266
+ h: string;
1267
+ l: string;
1268
+ c: string;
1269
+ /** Always true — the whole point of the event is that the candle closed. */
1270
+ final: boolean;
1271
+ /**
1272
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
1273
+ * entered the bid's band, and the price inside the band it entered at. The
1274
+ * engine's predicate is a range overlap, so this is neither the candle's
1275
+ * close nor a raw extreme. Both present or both absent — a server that
1276
+ * predates the field sends neither.
1277
+ */
1278
+ touchSec?: number;
1279
+ touchPrice?: string;
1280
+ };
1281
+ /** Pair the bid belongs to (see {@link MqttBidWonEvent.pairId}). */
1282
+ pairId?: string;
1283
+ }
1178
1284
  interface MqttBidLostEvent {
1179
1285
  bidId: string;
1180
1286
  /** Pair the lost bid belongs to (see {@link MqttBidWonEvent.pairId}). */
@@ -1268,6 +1374,7 @@ interface GameChannelEvents {
1268
1374
  bidAccepted: [MqttBidAcceptedEvent];
1269
1375
  bidWon: [MqttBidWonEvent];
1270
1376
  bidLost: [MqttBidLostEvent];
1377
+ cellCandleFinal: [MqttCellCandleFinalEvent];
1271
1378
  bidCancelled: [MqttBidCancelledEvent];
1272
1379
  balanceUpdate: [MqttBalanceEvent];
1273
1380
  configUpdate: [MqttConfigEvent];
@@ -1298,6 +1405,7 @@ declare class MigrationChannel extends EventEmitter<MigrationChannelEvents> {
1298
1405
  interface UserBidsChannelEvents {
1299
1406
  bidWon: [MqttBidWonEvent];
1300
1407
  bidLost: [MqttBidLostEvent];
1408
+ cellCandleFinal: [MqttCellCandleFinalEvent];
1301
1409
  error: [Error];
1302
1410
  }
1303
1411
  /**
@@ -1848,4 +1956,4 @@ declare function computeBaseline(candleClose: number, candleTimeSec: number, cel
1848
1956
  baselineTime: number;
1849
1957
  };
1850
1958
 
1851
- export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, DEFAULT_PROBE_TTL_SECONDS, DEFAULT_REGION, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, KNOWN_REGIONS, type KnownRegion, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, MigrationChannel, type MigrationChannelEvents, type MigrationCompletedEvent, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type ProbeNearestRegionOptions, REGION_PROBE_CACHE_KEY, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type RefreshTokenResult, type RegionDomainMap, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isKnownRegion, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, probeNearestRegion, resolveRegionBaseUrl, roundCoefToSignificantDigits };
1959
+ export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, type BidCellCandle, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, DEFAULT_PROBE_TTL_SECONDS, DEFAULT_REGION, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, KNOWN_REGIONS, type KnownRegion, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, MigrationChannel, type MigrationChannelEvents, type MigrationCompletedEvent, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttCellCandleFinalEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type ProbeNearestRegionOptions, REGION_PROBE_CACHE_KEY, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type RefreshTokenResult, type RegionDomainMap, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isKnownRegion, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, probeNearestRegion, resolveRegionBaseUrl, roundCoefToSignificantDigits };
package/dist/index.d.ts CHANGED
@@ -326,6 +326,39 @@ interface BidMeta {
326
326
  /** Display pair, e.g. "ETH/USD". */
327
327
  pairName: string;
328
328
  }
329
+ /**
330
+ * The OHLC of a bid's cell window — what price did inside [time1, time2] on the bid's
331
+ * own pair, reduced from every stored tick covering it.
332
+ *
333
+ * Values are Decimal strings, not numbers: the archive stores 8 decimal places and
334
+ * parsing to a JS number here would round them before the UI ever sees them.
335
+ */
336
+ interface BidCellCandle {
337
+ o: string;
338
+ h: string;
339
+ l: string;
340
+ c: string;
341
+ /**
342
+ * Whether the candle had CLOSED when it settled the bid.
343
+ *
344
+ * `false` = the values can still move: a 1m/5m win settles the instant the
345
+ * band is touched (the verdict is permanent) but the candle's O/C and extremes
346
+ * keep moving until it closes — delivered live over MQTT, and by `myBids` when
347
+ * the touching candle has not closed at read time. The UI shows such a value
348
+ * in the same not-yet-settled language the pending row uses. Absent (a server
349
+ * that predates the flag) means settled.
350
+ */
351
+ final?: boolean;
352
+ /**
353
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
354
+ * entered the bid's band, and the price inside the band it entered at — what
355
+ * the popover shows as "reached <price> at <time>". Both present or both
356
+ * absent: a bid that did not win has no single moment that won it, and a
357
+ * server predating the field sends neither.
358
+ */
359
+ touchSec?: number;
360
+ touchPrice?: string;
361
+ }
329
362
  interface Bid {
330
363
  id: string;
331
364
  userId: string;
@@ -363,6 +396,16 @@ interface Bid {
363
396
  * display pair instead of mapping `pairId` yourself.
364
397
  */
365
398
  meta?: BidMeta | null;
399
+ /**
400
+ * What price did inside this bid's cell window, served with the bid itself so a
401
+ * consumer never has to fetch it separately (openspec ux-260819-bid-detail-popover).
402
+ *
403
+ * Null in three cases a consumer should treat alike: the bid is still pending, its
404
+ * ticks are no longer stored, or the caller's query did not select the field. A
405
+ * cancelled bid DOES carry one — its window still elapsed — and the server attaches
406
+ * no verdict about what the outcome would have been.
407
+ */
408
+ cellCandle?: BidCellCandle | null;
366
409
  }
367
410
  interface CancelBidResult {
368
411
  bid: Bid;
@@ -1166,6 +1209,37 @@ interface MqttBidWonEvent {
1166
1209
  payout: string;
1167
1210
  userId: string;
1168
1211
  balance?: string;
1212
+ /**
1213
+ * OHLC of the candle whose band touch declared this win — the bid's
1214
+ * `cellCandle`, shipped with the result so the client never has to refetch the
1215
+ * bid list to learn it (openspec ux-260819, revised D2e).
1216
+ *
1217
+ * Sent on live wins and on D10 recovery alike (recovery hands over the stored
1218
+ * candle whose touch it found). Absent only on any grid-api older than that
1219
+ * change.
1220
+ */
1221
+ cellCandle?: {
1222
+ o: string;
1223
+ h: string;
1224
+ l: string;
1225
+ c: string;
1226
+ /**
1227
+ * Whether that candle had CLOSED when it settled the bid. A 1m/5m win settles
1228
+ * the instant the band is touched — safe, because a touch is permanent — but
1229
+ * the candle's O/C and extremes can still move until it closes. `false` means
1230
+ * the client must present the value as not-yet-settled rather than final.
1231
+ */
1232
+ final: boolean;
1233
+ /**
1234
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
1235
+ * entered the bid's band, and the price inside the band it entered at. The
1236
+ * engine's predicate is a range overlap, so this is neither the candle's
1237
+ * close nor a raw extreme. Both present or both absent — a server that
1238
+ * predates the field sends neither.
1239
+ */
1240
+ touchSec?: number;
1241
+ touchPrice?: string;
1242
+ };
1169
1243
  /**
1170
1244
  * Pair the won bid belongs to, e.g. "grid-ETH-USD". Derived from the result
1171
1245
  * topic (`game/{agencyId}:{pairId}/user/.../bid_result`) since the won payload
@@ -1175,6 +1249,38 @@ interface MqttBidWonEvent {
1175
1249
  */
1176
1250
  pairId?: string;
1177
1251
  }
1252
+ /**
1253
+ * Follow-up patch grid-api publishes when the window of a building-candle win
1254
+ * closes (wire type `cell_candle_final`): the CLOSED o/h/l/c of the candle the
1255
+ * bid settled on, replacing the in-progress values the `bidWon` event carried
1256
+ * with `cellCandle.final: false` (openspec ux-260819, design D2g).
1257
+ *
1258
+ * A patch, not a result: consumers must only update the bid's stored candle —
1259
+ * never replay win side effects (sound, payout, balance).
1260
+ */
1261
+ interface MqttCellCandleFinalEvent {
1262
+ bidId: string;
1263
+ userId: string;
1264
+ cellCandle: {
1265
+ o: string;
1266
+ h: string;
1267
+ l: string;
1268
+ c: string;
1269
+ /** Always true — the whole point of the event is that the candle closed. */
1270
+ final: boolean;
1271
+ /**
1272
+ * The WINNING TICK (TH-146 "success ticker"): the unix SECOND at which price
1273
+ * entered the bid's band, and the price inside the band it entered at. The
1274
+ * engine's predicate is a range overlap, so this is neither the candle's
1275
+ * close nor a raw extreme. Both present or both absent — a server that
1276
+ * predates the field sends neither.
1277
+ */
1278
+ touchSec?: number;
1279
+ touchPrice?: string;
1280
+ };
1281
+ /** Pair the bid belongs to (see {@link MqttBidWonEvent.pairId}). */
1282
+ pairId?: string;
1283
+ }
1178
1284
  interface MqttBidLostEvent {
1179
1285
  bidId: string;
1180
1286
  /** Pair the lost bid belongs to (see {@link MqttBidWonEvent.pairId}). */
@@ -1268,6 +1374,7 @@ interface GameChannelEvents {
1268
1374
  bidAccepted: [MqttBidAcceptedEvent];
1269
1375
  bidWon: [MqttBidWonEvent];
1270
1376
  bidLost: [MqttBidLostEvent];
1377
+ cellCandleFinal: [MqttCellCandleFinalEvent];
1271
1378
  bidCancelled: [MqttBidCancelledEvent];
1272
1379
  balanceUpdate: [MqttBalanceEvent];
1273
1380
  configUpdate: [MqttConfigEvent];
@@ -1298,6 +1405,7 @@ declare class MigrationChannel extends EventEmitter<MigrationChannelEvents> {
1298
1405
  interface UserBidsChannelEvents {
1299
1406
  bidWon: [MqttBidWonEvent];
1300
1407
  bidLost: [MqttBidLostEvent];
1408
+ cellCandleFinal: [MqttCellCandleFinalEvent];
1301
1409
  error: [Error];
1302
1410
  }
1303
1411
  /**
@@ -1848,4 +1956,4 @@ declare function computeBaseline(candleClose: number, candleTimeSec: number, cel
1848
1956
  baselineTime: number;
1849
1957
  };
1850
1958
 
1851
- export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, DEFAULT_PROBE_TTL_SECONDS, DEFAULT_REGION, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, KNOWN_REGIONS, type KnownRegion, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, MigrationChannel, type MigrationChannelEvents, type MigrationCompletedEvent, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type ProbeNearestRegionOptions, REGION_PROBE_CACHE_KEY, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type RefreshTokenResult, type RegionDomainMap, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isKnownRegion, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, probeNearestRegion, resolveRegionBaseUrl, roundCoefToSignificantDigits };
1959
+ export { type AgencyPairFilter, AgencyPairModule, type AgencyPairStats, type AgencyPairStatus, AuthModule, type BackendHealth, type Bid, type BidCellCandle, BidModule, type BidStatus, CANDLE_EVENT, type CancelBidResult, type Candle, type CandleEventType, type CoefficientInput, type Constraints, type Currency, DEFAULT_CHART_HISTORY_LIMIT, DEFAULT_PROBE_TTL_SECONDS, DEFAULT_REGION, type ErrorMessageCatalog, type ErrorMessageMeta, type ErrorMessagesResponse, GameChannel, type GameConfig, type GridConfig, KNOWN_REGIONS, type KnownRegion, type LeaderboardEntry, LeaderboardModule, type LeaderboardPeriod, type LeaderboardSortBy, LocaleModule, type LocaleRefreshResult, type LocaleResponse, type LocaleTranslations, type LoginResult, type Me, MigrationChannel, type MigrationChannelEvents, type MigrationCompletedEvent, type MqttAcceptedBid, type MqttAgencyPairStatsEvent, type MqttAuthConfig, type MqttBalanceEvent, type MqttBidAcceptedEvent, type MqttBidLostEvent, type MqttBidWonEvent, type MqttCandleEvent, type MqttCellCandleFinalEvent, type MqttConfigEvent, type MqttIdealConfigEvent, type MqttWalletBalanceEvent, type MyRank, type NetworkLevel, type NetworkQuality, NetworkQualityMonitor, type Pair, type PairInfo, PairModule, type PlaceBidInput, type ProbeNearestRegionOptions, REGION_PROBE_CACHE_KEY, type RankBoard, type RankEntry, type RankPeriod, type RankSort, RealtimeModule, type RefreshTokenResult, type RegionDomainMap, type Sample, type SampleReason, type SignalSource, type SortDir, TaphubAuthError, TaphubClient, type TaphubClientConfig, TaphubError, TaphubEventBus, type TaphubEventMap, TaphubNetworkError, TaphubServerError, TaphubSlippageError, TaphubStorage, type TaphubStorageAdapter, TaphubValidationError, type User, UserBidsChannel, type UserBidsChannelEvents, UserModule, type UserPnL, type UserPnLPeriod, type Wallet as UserWallet, type Wallet$1 as Wallet, type WalletBalanceReason, WalletChannel, adaptiveSimpson, autoDetectStorage, calculateCoefficientWrapper, calculateProbHit, calculateProbWin, calculateProbWin_v2, computeBaseline, errorFunction, isCancelled, isKnownRegion, isLoss, isPending, isTerminal, isWin, normalCDF, normalPDF, normaliseLang, pairIdFromBidResultTopic, probeNearestRegion, resolveRegionBaseUrl, roundCoefToSignificantDigits };
package/dist/index.js CHANGED
@@ -581,7 +581,22 @@ function normaliseBid(node) {
581
581
  refundAmount: node.refund_amount ?? null,
582
582
  slippage: node.slippage,
583
583
  createdAt: node.created_at || null,
584
- meta: parseMeta(node.meta)
584
+ meta: parseMeta(node.meta),
585
+ // Absent and explicit null both mean "no candle" — an older server, an unselected
586
+ // field, a pending bid and purged ticks must all read the same to a consumer.
587
+ // `final` null (nullable Boolean) collapses to absent = settled, so only a real
588
+ // `false` puts the row in the in-progress presentation.
589
+ cellCandle: node.cellCandle ? {
590
+ o: node.cellCandle.o,
591
+ h: node.cellCandle.h,
592
+ l: node.cellCandle.l,
593
+ c: node.cellCandle.c,
594
+ ...typeof node.cellCandle.final === "boolean" ? { final: node.cellCandle.final } : {},
595
+ // Both legs or neither — see BidCellCandle.touchSec. Nulls (a non-win,
596
+ // or an older server) collapse to absent so a consumer only has to
597
+ // check one of them.
598
+ ...typeof node.cellCandle.touchSec === "number" && typeof node.cellCandle.touchPrice === "string" ? { touchSec: node.cellCandle.touchSec, touchPrice: node.cellCandle.touchPrice } : {}
599
+ } : null
585
600
  };
586
601
  }
587
602
  function normaliseBids(list) {
@@ -602,6 +617,7 @@ var PLACE_BID_MUTATION = `mutation PlaceBid($input: PlaceBidInput!) {
602
617
  var MY_BIDS_QUERY = `query MyBids($statuses: [BidStatus!], $limit: Int, $offset: Int, $pairId: ID) {
603
618
  myBids(statuses: $statuses, limit: $limit, offset: $offset, pairId: $pairId) {
604
619
  id user_id pair_id currency amount coefficient time1 time2 price1 price2 status payout refund_rate refund_amount slippage created_at meta
620
+ cellCandle { o h l c final touchSec touchPrice }
605
621
  }
606
622
  }`;
607
623
  var CANCEL_BID_MUTATION = `mutation CancelBid($input: CancelBidInput!) {
@@ -1769,6 +1785,23 @@ function mapWireBidResult(raw, topicPairId) {
1769
1785
  userId: p.user_id
1770
1786
  };
1771
1787
  if (typeof p.balance === "string") data.balance = p.balance;
1788
+ const candle = p.cellCandle;
1789
+ if (candle && typeof candle.o === "string" && typeof candle.h === "string" && typeof candle.l === "string" && typeof candle.c === "string") {
1790
+ data.cellCandle = {
1791
+ o: candle.o,
1792
+ h: candle.h,
1793
+ l: candle.l,
1794
+ c: candle.c,
1795
+ final: candle.final !== "false"
1796
+ };
1797
+ if (typeof candle.touchSec === "string" && typeof candle.touchPrice === "string") {
1798
+ const touchSec = Number.parseInt(candle.touchSec, 10);
1799
+ if (Number.isFinite(touchSec)) {
1800
+ data.cellCandle.touchSec = touchSec;
1801
+ data.cellCandle.touchPrice = candle.touchPrice;
1802
+ }
1803
+ }
1804
+ }
1772
1805
  if (topicPairId !== void 0) data.pairId = topicPairId;
1773
1806
  return { event: "bidWon", data };
1774
1807
  }
@@ -1777,6 +1810,32 @@ function mapWireBidResult(raw, topicPairId) {
1777
1810
  if (topicPairId !== void 0) data.pairId = topicPairId;
1778
1811
  return { event: "bidLost", data };
1779
1812
  }
1813
+ case "cell_candle_final": {
1814
+ const candle = p.cellCandle;
1815
+ if (!candle || typeof candle.o !== "string" || typeof candle.h !== "string" || typeof candle.l !== "string" || typeof candle.c !== "string") {
1816
+ return null;
1817
+ }
1818
+ const data = {
1819
+ bidId: p.bidId,
1820
+ userId: p.user_id,
1821
+ cellCandle: {
1822
+ o: candle.o,
1823
+ h: candle.h,
1824
+ l: candle.l,
1825
+ c: candle.c,
1826
+ final: candle.final !== "false"
1827
+ }
1828
+ };
1829
+ if (typeof candle.touchSec === "string" && typeof candle.touchPrice === "string") {
1830
+ const touchSec = Number.parseInt(candle.touchSec, 10);
1831
+ if (Number.isFinite(touchSec)) {
1832
+ data.cellCandle.touchSec = touchSec;
1833
+ data.cellCandle.touchPrice = candle.touchPrice;
1834
+ }
1835
+ }
1836
+ if (topicPairId !== void 0) data.pairId = topicPairId;
1837
+ return { event: "cellCandleFinal", data };
1838
+ }
1780
1839
  default:
1781
1840
  console.warn(`Unknown bid_result type "${String(p.type)}"`);
1782
1841
  return null;
@@ -1894,6 +1953,7 @@ var RealtimeModule = class extends EventEmitter5 {
1894
1953
  else if (mapped.event === "bidAccepted") channel.emit("bidAccepted", mapped.data);
1895
1954
  else if (mapped.event === "bidWon") channel.emit("bidWon", mapped.data);
1896
1955
  else if (mapped.event === "bidLost") channel.emit("bidLost", mapped.data);
1956
+ else if (mapped.event === "cellCandleFinal") channel.emit("cellCandleFinal", mapped.data);
1897
1957
  else if (mapped.event === "balanceUpdate") channel.emit("balanceUpdate", mapped.data);
1898
1958
  else if (mapped.event === "configUpdate") channel.emit("configUpdate", mapped.data);
1899
1959
  else if (mapped.event === "idealConfigUpdate")
@@ -2026,6 +2086,7 @@ var RealtimeModule = class extends EventEmitter5 {
2026
2086
  if (!mapped) return;
2027
2087
  if (mapped.event === "bidWon") channel.emit("bidWon", mapped.data);
2028
2088
  else if (mapped.event === "bidLost") channel.emit("bidLost", mapped.data);
2089
+ else if (mapped.event === "cellCandleFinal") channel.emit("cellCandleFinal", mapped.data);
2029
2090
  };
2030
2091
  const onError = (err) => {
2031
2092
  channel.emit("error", err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taphubhq/sdk-core",
3
- "version": "0.26.1",
3
+ "version": "0.27.0",
4
4
  "description": "Core SDK for building on the TabHub platform",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",