@alpha-arcade/sdk 0.3.1 → 0.3.6

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/dist/index.js CHANGED
@@ -2103,6 +2103,7 @@ var calculateMatchingOrders = (orderbook, isBuying, isYes, quantity, price, slip
2103
2103
 
2104
2104
  // src/constants.ts
2105
2105
  var DEFAULT_API_BASE_URL = "https://platform.alphaarcade.com/api";
2106
+ var DEFAULT_WSS_BASE_URL = "wss://wss.platform.alphaarcade.com";
2106
2107
  var DEFAULT_MARKET_CREATOR_ADDRESS = "5P5Y6HTWUNG2E3VXBQDZN3ENZD3JPAIR5PKT3LOYJAPAUKOLFD6KANYTRY";
2107
2108
 
2108
2109
  // src/modules/orderbook.ts
@@ -2243,6 +2244,18 @@ var getWalletOrdersFromApi = async (config, walletAddress) => {
2243
2244
  }
2244
2245
  return allOrders;
2245
2246
  };
2247
+ var getFullOrderbookFromApi = async (config, marketId) => {
2248
+ if (!config.apiKey) {
2249
+ throw new Error("apiKey is required for API-based orderbook fetching. Retrieve an API key from the Alpha Arcade platform via the Account page and pass it to the client.");
2250
+ }
2251
+ const baseUrl = config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
2252
+ const url = `${baseUrl}/get-full-orderbook?marketId=${encodeURIComponent(marketId)}`;
2253
+ const response = await fetch(url, { headers: { "x-api-key": config.apiKey } });
2254
+ if (!response.ok) {
2255
+ throw new Error(`Alpha API error: ${response.status} ${response.statusText}`);
2256
+ }
2257
+ return response.json();
2258
+ };
2246
2259
 
2247
2260
  // src/modules/trading.ts
2248
2261
  var extractEscrowAppId = async (algodClient, indexerClient, targetTxId) => {
@@ -3067,12 +3080,18 @@ var getMarketFromApi = async (config, marketId) => {
3067
3080
  }
3068
3081
  return market;
3069
3082
  };
3083
+ var hasRewardLiquidity = (market) => {
3084
+ if ((market.totalRewards ?? 0) > 0 || (market.totalPregameRewards ?? 0) > 0) {
3085
+ return true;
3086
+ }
3087
+ return (market.options ?? []).some((option) => (option.totalRewards ?? 0) > 0 || (option.totalPregameRewards ?? 0) > 0);
3088
+ };
3070
3089
  var getRewardMarkets = async (config) => {
3071
3090
  if (!config.apiKey) {
3072
3091
  throw new Error("apiKey is required for API-based market fetching. Retrieve an API key from the Alpha Arcade platform via the Account page and pass it to the client.");
3073
3092
  }
3074
3093
  const markets = await getLiveMarketsFromApi(config);
3075
- return markets.filter((m) => m.totalRewards && m.totalRewards > 0);
3094
+ return markets.filter((market) => hasRewardLiquidity(market));
3076
3095
  };
3077
3096
  var getLiveMarkets = async (config) => {
3078
3097
  if (config.apiKey) {
@@ -3249,6 +3268,19 @@ var AlphaClient = class {
3249
3268
  async getOrderbook(marketAppId) {
3250
3269
  return getOrderbook(this.config, marketAppId);
3251
3270
  }
3271
+ /**
3272
+ * Fetches the full processed orderbook snapshot for a market from the Alpha REST API.
3273
+ *
3274
+ * Returns the same shape as websocket `orderbook_changed.orderbook`: a record keyed by
3275
+ * `marketAppId`, where each value includes aggregated bids/asks plus detailed yes/no orders.
3276
+ * Requires `apiKey`.
3277
+ *
3278
+ * @param marketId - The Alpha market UUID
3279
+ * @returns Full processed market orderbook keyed by marketAppId
3280
+ */
3281
+ async getFullOrderbookFromApi(marketId) {
3282
+ return getFullOrderbookFromApi(this.config, marketId);
3283
+ }
3252
3284
  /**
3253
3285
  * Gets open orders for a specific wallet on a market.
3254
3286
  *
@@ -3344,6 +3376,310 @@ var AlphaClient = class {
3344
3376
  }
3345
3377
  };
3346
3378
 
3347
- export { AlphaClient, DEFAULT_API_BASE_URL, DEFAULT_MARKET_CREATOR_ADDRESS, calculateFee, calculateFeeFromTotal, calculateMatchingOrders, checkAssetOptIn, decodeGlobalState, getEscrowGlobalState, getLiveMarketsFromApi, getMarketFromApi, getMarketGlobalState, getMarketOnChain, getMarketsOnChain };
3379
+ // src/websocket.ts
3380
+ var WS_OPEN = 1;
3381
+ var resolveWebSocket = (provided) => {
3382
+ if (provided) return provided;
3383
+ if (typeof globalThis !== "undefined" && globalThis.WebSocket) {
3384
+ return globalThis.WebSocket;
3385
+ }
3386
+ throw new Error(
3387
+ 'No WebSocket implementation found. On Node.js < 22, install the "ws" package and pass it: new AlphaWebSocket({ WebSocket: require("ws") })'
3388
+ );
3389
+ };
3390
+ var AlphaWebSocket = class {
3391
+ url;
3392
+ reconnectEnabled;
3393
+ maxReconnectAttempts;
3394
+ heartbeatIntervalMs;
3395
+ WebSocketImpl;
3396
+ ws = null;
3397
+ subscriptions = /* @__PURE__ */ new Map();
3398
+ pendingRequests = /* @__PURE__ */ new Map();
3399
+ lastOrderbookVersionBySubscription = /* @__PURE__ */ new Map();
3400
+ heartbeatTimer = null;
3401
+ reconnectTimer = null;
3402
+ reconnectAttempts = 0;
3403
+ intentionallyClosed = false;
3404
+ connectPromise = null;
3405
+ constructor(config) {
3406
+ this.url = config?.url ?? DEFAULT_WSS_BASE_URL;
3407
+ this.reconnectEnabled = config?.reconnect ?? true;
3408
+ this.maxReconnectAttempts = config?.maxReconnectAttempts ?? Infinity;
3409
+ this.heartbeatIntervalMs = config?.heartbeatIntervalMs ?? 6e4;
3410
+ this.WebSocketImpl = resolveWebSocket(config?.WebSocket);
3411
+ }
3412
+ /** Whether the WebSocket is currently open and connected */
3413
+ get connected() {
3414
+ return this.ws?.readyState === WS_OPEN;
3415
+ }
3416
+ // ============================================
3417
+ // Subscribe Methods
3418
+ // ============================================
3419
+ /**
3420
+ * Subscribe to live market probability updates (incremental diffs).
3421
+ * @returns An unsubscribe function
3422
+ */
3423
+ subscribeLiveMarkets(callback) {
3424
+ return this.subscribe("get-live-markets", {}, "markets_changed", callback);
3425
+ }
3426
+ /**
3427
+ * Subscribe to change events for a single market.
3428
+ * @param slug - The market slug
3429
+ * @returns An unsubscribe function
3430
+ */
3431
+ subscribeMarket(slug, callback) {
3432
+ return this.subscribe("get-market", { slug }, "market_changed", callback);
3433
+ }
3434
+ /**
3435
+ * Subscribe to full orderbook snapshots (~5s interval on changes).
3436
+ * @param slug - The market slug
3437
+ * @returns An unsubscribe function
3438
+ */
3439
+ subscribeOrderbook(slug, callback) {
3440
+ return this.subscribe("get-orderbook", { slug }, "orderbook_changed", callback);
3441
+ }
3442
+ /**
3443
+ * Subscribe to wallet order updates.
3444
+ * @param wallet - The wallet address
3445
+ * @returns An unsubscribe function
3446
+ */
3447
+ subscribeWalletOrders(wallet, callback) {
3448
+ return this.subscribe("get-wallet-orders", { wallet }, "wallet_orders_changed", callback);
3449
+ }
3450
+ // ============================================
3451
+ // Control Methods
3452
+ // ============================================
3453
+ /** Query the server for the list of active subscriptions on this connection */
3454
+ listSubscriptions() {
3455
+ return this.sendRequest({ method: "LIST_SUBSCRIPTIONS" });
3456
+ }
3457
+ /** Query a server property (e.g. "heartbeat", "limits") */
3458
+ getProperty(property) {
3459
+ return this.sendRequest({ method: "GET_PROPERTY", params: [property] });
3460
+ }
3461
+ // ============================================
3462
+ // Lifecycle
3463
+ // ============================================
3464
+ /** Open the WebSocket connection. Called automatically on first subscribe. */
3465
+ connect() {
3466
+ if (this.connectPromise) return this.connectPromise;
3467
+ this.connectPromise = this.doConnect();
3468
+ return this.connectPromise;
3469
+ }
3470
+ /** Close the connection and clean up all resources */
3471
+ close() {
3472
+ this.intentionallyClosed = true;
3473
+ this.clearTimers();
3474
+ this.subscriptions.clear();
3475
+ for (const [, req] of this.pendingRequests) {
3476
+ clearTimeout(req.timer);
3477
+ req.reject(new Error("WebSocket closed"));
3478
+ }
3479
+ this.pendingRequests.clear();
3480
+ if (this.ws) {
3481
+ this.ws.close();
3482
+ this.ws = null;
3483
+ }
3484
+ this.connectPromise = null;
3485
+ }
3486
+ // ============================================
3487
+ // Internal
3488
+ // ============================================
3489
+ buildStreamKey(stream, params) {
3490
+ const parts = [stream, ...Object.entries(params).sort().map(([k, v]) => `${k}=${v}`)];
3491
+ return parts.join("&");
3492
+ }
3493
+ buildQueryString() {
3494
+ const subs = [...this.subscriptions.values()];
3495
+ if (subs.length === 0) return "";
3496
+ const first = subs[0];
3497
+ const params = new URLSearchParams({ stream: first.stream, ...first.params });
3498
+ return "?" + params.toString();
3499
+ }
3500
+ subscribe(stream, params, eventType, callback) {
3501
+ const key = this.buildStreamKey(stream, params);
3502
+ this.subscriptions.set(key, { stream, params, callback, eventType });
3503
+ if (this.connected) {
3504
+ this.sendSubscribe(stream, params);
3505
+ } else {
3506
+ this.connect();
3507
+ }
3508
+ return () => {
3509
+ this.subscriptions.delete(key);
3510
+ this.lastOrderbookVersionBySubscription.delete(key);
3511
+ if (this.connected) {
3512
+ this.sendUnsubscribe(stream, params);
3513
+ }
3514
+ };
3515
+ }
3516
+ async doConnect() {
3517
+ this.intentionallyClosed = false;
3518
+ return new Promise((resolve, reject) => {
3519
+ const qs = this.buildQueryString();
3520
+ const ws = new this.WebSocketImpl(this.url + qs);
3521
+ ws.onopen = () => {
3522
+ this.ws = ws;
3523
+ this.reconnectAttempts = 0;
3524
+ this.startHeartbeat();
3525
+ const subs = [...this.subscriptions.values()];
3526
+ for (const sub of subs) {
3527
+ this.sendSubscribe(sub.stream, sub.params);
3528
+ }
3529
+ resolve();
3530
+ };
3531
+ ws.onmessage = (event) => {
3532
+ this.handleMessage(event.data);
3533
+ };
3534
+ ws.onclose = () => {
3535
+ this.ws = null;
3536
+ this.connectPromise = null;
3537
+ this.stopHeartbeat();
3538
+ if (!this.intentionallyClosed) {
3539
+ this.scheduleReconnect();
3540
+ }
3541
+ };
3542
+ ws.onerror = (err) => {
3543
+ if (!this.ws) {
3544
+ reject(new Error("WebSocket connection failed"));
3545
+ }
3546
+ };
3547
+ });
3548
+ }
3549
+ handleMessage(raw) {
3550
+ let msg;
3551
+ try {
3552
+ msg = JSON.parse(raw);
3553
+ } catch {
3554
+ return;
3555
+ }
3556
+ if (msg.type === "ping") {
3557
+ this.send({ method: "PONG" });
3558
+ return;
3559
+ }
3560
+ const responseId = typeof msg.id === "string" ? msg.id : typeof msg.requestId === "string" ? msg.requestId : null;
3561
+ if (responseId && this.pendingRequests.has(responseId)) {
3562
+ const req = this.pendingRequests.get(responseId);
3563
+ this.pendingRequests.delete(responseId);
3564
+ clearTimeout(req.timer);
3565
+ req.resolve(msg);
3566
+ return;
3567
+ }
3568
+ const eventType = msg.type;
3569
+ if (!eventType) return;
3570
+ for (const [key, sub] of this.subscriptions.entries()) {
3571
+ if (!this.matchesSubscriptionMessage(sub, msg)) {
3572
+ continue;
3573
+ }
3574
+ if (!this.shouldDispatchSubscriptionMessage(key, sub, msg)) {
3575
+ continue;
3576
+ }
3577
+ try {
3578
+ sub.callback(msg);
3579
+ } catch {
3580
+ }
3581
+ }
3582
+ }
3583
+ matchesSubscriptionMessage(sub, msg) {
3584
+ if (sub.eventType !== msg.type) return false;
3585
+ if (msg.type === "orderbook_changed") {
3586
+ const messageMarketId = typeof msg.marketId === "string" ? msg.marketId : "";
3587
+ const messageSlug = typeof msg.slug === "string" ? msg.slug : "";
3588
+ const subscriptionMarketId = typeof sub.params.marketId === "string" ? sub.params.marketId : "";
3589
+ const subscriptionSlug = typeof sub.params.slug === "string" ? sub.params.slug : "";
3590
+ if (subscriptionMarketId) return subscriptionMarketId === messageMarketId;
3591
+ if (subscriptionSlug && messageSlug) return subscriptionSlug === messageSlug;
3592
+ if (subscriptionSlug || subscriptionMarketId) return false;
3593
+ }
3594
+ if (msg.type === "wallet_orders_changed") {
3595
+ const messageWallet = typeof msg.wallet === "string" ? msg.wallet : "";
3596
+ const subscriptionWallet = typeof sub.params.wallet === "string" ? sub.params.wallet : "";
3597
+ if (subscriptionWallet) return subscriptionWallet === messageWallet;
3598
+ return false;
3599
+ }
3600
+ return true;
3601
+ }
3602
+ shouldDispatchSubscriptionMessage(key, sub, msg) {
3603
+ if (sub.eventType !== "orderbook_changed") {
3604
+ return true;
3605
+ }
3606
+ const version = Number(msg.version ?? 0);
3607
+ if (!Number.isFinite(version)) {
3608
+ return true;
3609
+ }
3610
+ const lastVersion = this.lastOrderbookVersionBySubscription.get(key) ?? 0;
3611
+ if (version < lastVersion) {
3612
+ return false;
3613
+ }
3614
+ this.lastOrderbookVersionBySubscription.set(key, version);
3615
+ return true;
3616
+ }
3617
+ sendSubscribe(stream, params) {
3618
+ this.send({ method: "SUBSCRIBE", params: [{ stream, ...params }] });
3619
+ }
3620
+ sendUnsubscribe(stream, params) {
3621
+ this.send({ method: "UNSUBSCRIBE", params: [{ stream, ...params }] });
3622
+ }
3623
+ sendRequest(payload, timeoutMs = 1e4) {
3624
+ const requestId = crypto.randomUUID();
3625
+ return new Promise((resolve, reject) => {
3626
+ const timer = setTimeout(() => {
3627
+ this.pendingRequests.delete(requestId);
3628
+ reject(new Error("Request timed out"));
3629
+ }, timeoutMs);
3630
+ this.pendingRequests.set(requestId, { resolve, reject, timer });
3631
+ if (!this.connected) {
3632
+ this.connect().then(() => {
3633
+ this.send({ ...payload, id: requestId });
3634
+ }).catch(reject);
3635
+ } else {
3636
+ this.send({ ...payload, id: requestId });
3637
+ }
3638
+ });
3639
+ }
3640
+ send(data) {
3641
+ if (this.ws?.readyState === WS_OPEN) {
3642
+ this.ws.send(JSON.stringify(data));
3643
+ }
3644
+ }
3645
+ // ============================================
3646
+ // Heartbeat
3647
+ // ============================================
3648
+ startHeartbeat() {
3649
+ this.stopHeartbeat();
3650
+ this.heartbeatTimer = setInterval(() => {
3651
+ this.send({ method: "PING" });
3652
+ }, this.heartbeatIntervalMs);
3653
+ }
3654
+ stopHeartbeat() {
3655
+ if (this.heartbeatTimer) {
3656
+ clearInterval(this.heartbeatTimer);
3657
+ this.heartbeatTimer = null;
3658
+ }
3659
+ }
3660
+ // ============================================
3661
+ // Reconnect
3662
+ // ============================================
3663
+ scheduleReconnect() {
3664
+ if (!this.reconnectEnabled) return;
3665
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
3666
+ const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
3667
+ this.reconnectAttempts++;
3668
+ this.reconnectTimer = setTimeout(() => {
3669
+ this.reconnectTimer = null;
3670
+ this.connect().catch(() => {
3671
+ });
3672
+ }, delay);
3673
+ }
3674
+ clearTimers() {
3675
+ this.stopHeartbeat();
3676
+ if (this.reconnectTimer) {
3677
+ clearTimeout(this.reconnectTimer);
3678
+ this.reconnectTimer = null;
3679
+ }
3680
+ }
3681
+ };
3682
+
3683
+ export { AlphaClient, AlphaWebSocket, DEFAULT_API_BASE_URL, DEFAULT_MARKET_CREATOR_ADDRESS, DEFAULT_WSS_BASE_URL, calculateFee, calculateFeeFromTotal, calculateMatchingOrders, checkAssetOptIn, decodeGlobalState, getEscrowGlobalState, getLiveMarketsFromApi, getMarketFromApi, getMarketGlobalState, getMarketOnChain, getMarketsOnChain };
3348
3684
  //# sourceMappingURL=index.js.map
3349
3685
  //# sourceMappingURL=index.js.map