@alpha-arcade/sdk 0.3.0 → 0.3.5
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 +164 -2
- package/dist/index.cjs +467 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -2
- package/dist/index.d.ts +217 -2
- package/dist/index.js +466 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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) => {
|
|
@@ -2492,6 +2505,89 @@ var proposeMatch = async (config, params) => {
|
|
|
2492
2505
|
confirmedRound: Number(result.confirmedRound)
|
|
2493
2506
|
};
|
|
2494
2507
|
};
|
|
2508
|
+
var processMatch = async (config, params) => {
|
|
2509
|
+
const { algodClient, signer, activeAddress, usdcAssetId } = config;
|
|
2510
|
+
const { marketAppId, makerEscrowAppId, takerEscrowAppId } = params;
|
|
2511
|
+
const [marketState, makerAppInfo, takerAppInfo] = await Promise.all([
|
|
2512
|
+
getMarketGlobalState(algodClient, marketAppId),
|
|
2513
|
+
algodClient.getApplicationByID(makerEscrowAppId).do(),
|
|
2514
|
+
algodClient.getApplicationByID(takerEscrowAppId).do()
|
|
2515
|
+
]);
|
|
2516
|
+
const marketFeeAddress = marketState.fee_address;
|
|
2517
|
+
const yesAssetId = marketState.yes_asset_id;
|
|
2518
|
+
const noAssetId = marketState.no_asset_id;
|
|
2519
|
+
const makerState = decodeGlobalState(
|
|
2520
|
+
makerAppInfo.params?.globalState ?? makerAppInfo.params?.["global-state"] ?? []
|
|
2521
|
+
);
|
|
2522
|
+
const takerState = decodeGlobalState(
|
|
2523
|
+
takerAppInfo.params?.globalState ?? takerAppInfo.params?.["global-state"] ?? []
|
|
2524
|
+
);
|
|
2525
|
+
const makerOwner = makerState.owner ?? "";
|
|
2526
|
+
const takerOwner = takerState.owner ?? "";
|
|
2527
|
+
const makerRemaining = (makerState.quantity ?? 0) - (makerState.quantity_filled ?? 0);
|
|
2528
|
+
const takerRemaining = (takerState.quantity ?? 0) - (takerState.quantity_filled ?? 0);
|
|
2529
|
+
const matchedQuantity = Math.min(makerRemaining, takerRemaining);
|
|
2530
|
+
if (matchedQuantity <= 0) {
|
|
2531
|
+
throw new Error("processMatch: no quantity left to match on maker or taker escrow.");
|
|
2532
|
+
}
|
|
2533
|
+
const signerAccount = { signer, addr: activeAddress };
|
|
2534
|
+
const makerEscrowClient = new EscrowAppClient(
|
|
2535
|
+
{ resolveBy: "id", id: makerEscrowAppId, sender: signerAccount },
|
|
2536
|
+
algodClient
|
|
2537
|
+
);
|
|
2538
|
+
const takerEscrowClient = new EscrowAppClient(
|
|
2539
|
+
{ resolveBy: "id", id: takerEscrowAppId, sender: signerAccount },
|
|
2540
|
+
algodClient
|
|
2541
|
+
);
|
|
2542
|
+
const marketClient = new MarketAppClient(
|
|
2543
|
+
{ resolveBy: "id", id: marketAppId, sender: signerAccount },
|
|
2544
|
+
algodClient
|
|
2545
|
+
);
|
|
2546
|
+
const atc = new AtomicTransactionComposer();
|
|
2547
|
+
const assets = [usdcAssetId, yesAssetId, noAssetId];
|
|
2548
|
+
const sendParamsWithFee = { skipSending: true, fee: algokit4.microAlgos(3e3) };
|
|
2549
|
+
const sendParamsNoop = { skipSending: true, fee: algokit4.microAlgos(1e3) };
|
|
2550
|
+
const matchMakerTxn = await makerEscrowClient.matchMaker(
|
|
2551
|
+
{ taker: takerEscrowAppId, matchQuantity: matchedQuantity },
|
|
2552
|
+
{
|
|
2553
|
+
assets,
|
|
2554
|
+
apps: [marketAppId],
|
|
2555
|
+
accounts: [takerOwner, marketFeeAddress],
|
|
2556
|
+
sendParams: sendParamsWithFee
|
|
2557
|
+
}
|
|
2558
|
+
);
|
|
2559
|
+
atc.addTransaction({ txn: matchMakerTxn.transaction, signer });
|
|
2560
|
+
const matchTakerTxn = await takerEscrowClient.matchTaker(
|
|
2561
|
+
{ maker: makerEscrowAppId },
|
|
2562
|
+
{
|
|
2563
|
+
assets,
|
|
2564
|
+
apps: [marketAppId],
|
|
2565
|
+
accounts: [makerOwner, marketFeeAddress],
|
|
2566
|
+
sendParams: sendParamsWithFee
|
|
2567
|
+
}
|
|
2568
|
+
);
|
|
2569
|
+
atc.addTransaction({ txn: matchTakerTxn.transaction, signer });
|
|
2570
|
+
const processMatchTxn = await marketClient.processPotentialMatch(
|
|
2571
|
+
{ maker: makerEscrowAppId, taker: takerEscrowAppId },
|
|
2572
|
+
{
|
|
2573
|
+
assets,
|
|
2574
|
+
accounts: [makerOwner, takerOwner, marketFeeAddress],
|
|
2575
|
+
sendParams: sendParamsNoop
|
|
2576
|
+
}
|
|
2577
|
+
);
|
|
2578
|
+
atc.addTransaction({ txn: processMatchTxn.transaction, signer });
|
|
2579
|
+
const doNoopTxn = await marketClient.doNoop(
|
|
2580
|
+
{ callNumber: 1 },
|
|
2581
|
+
{ sendParams: sendParamsNoop }
|
|
2582
|
+
);
|
|
2583
|
+
atc.addTransaction({ txn: doNoopTxn.transaction, signer });
|
|
2584
|
+
const result = await atc.execute(algodClient, 4);
|
|
2585
|
+
return {
|
|
2586
|
+
success: true,
|
|
2587
|
+
txIds: result.txIDs,
|
|
2588
|
+
confirmedRound: Number(result.confirmedRound)
|
|
2589
|
+
};
|
|
2590
|
+
};
|
|
2495
2591
|
var amendOrder = async (config, params) => {
|
|
2496
2592
|
const { algodClient, signer, activeAddress, usdcAssetId } = config;
|
|
2497
2593
|
const { marketAppId, escrowAppId, price, quantity, slippage = 0 } = params;
|
|
@@ -2548,10 +2644,47 @@ var amendOrder = async (config, params) => {
|
|
|
2548
2644
|
);
|
|
2549
2645
|
atc.addTransaction({ txn: amendCall.transaction, signer });
|
|
2550
2646
|
const result = await atc.execute(algodClient, 4);
|
|
2647
|
+
const allTxIds = [...result.txIDs];
|
|
2648
|
+
let lastConfirmedRound = Number(result.confirmedRound);
|
|
2649
|
+
const orderbook = await getOrderbook(config, marketAppId);
|
|
2650
|
+
let matchingOrders = calculateMatchingOrders(orderbook, isBuy, position === 1, quantity, price, slippage);
|
|
2651
|
+
matchingOrders = matchingOrders.filter((m) => m.escrowAppId !== escrowAppId);
|
|
2652
|
+
if (matchingOrders.length > 0) {
|
|
2653
|
+
let quantityLeft = quantity;
|
|
2654
|
+
const matchedQuantities = [];
|
|
2655
|
+
const matchedPrices = [];
|
|
2656
|
+
for (const m of matchingOrders) {
|
|
2657
|
+
if (quantityLeft <= 0) break;
|
|
2658
|
+
try {
|
|
2659
|
+
const matchResult = await processMatch(config, {
|
|
2660
|
+
marketAppId,
|
|
2661
|
+
makerEscrowAppId: m.escrowAppId,
|
|
2662
|
+
takerEscrowAppId: escrowAppId
|
|
2663
|
+
});
|
|
2664
|
+
allTxIds.push(...matchResult.txIds);
|
|
2665
|
+
lastConfirmedRound = matchResult.confirmedRound;
|
|
2666
|
+
const q = Math.min(m.quantity, quantityLeft);
|
|
2667
|
+
matchedQuantities.push(q);
|
|
2668
|
+
matchedPrices.push((m.price ?? price) * q);
|
|
2669
|
+
quantityLeft -= q;
|
|
2670
|
+
} catch (err) {
|
|
2671
|
+
console.log(`Error matching order: ${JSON.stringify(err)}`);
|
|
2672
|
+
break;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
const totalMatchedQuantity = matchedQuantities.reduce((a, b) => a + b, 0);
|
|
2676
|
+
const matchedPrice = totalMatchedQuantity > 0 ? Math.round(matchedPrices.reduce((a, b) => a + b, 0) / totalMatchedQuantity) : void 0;
|
|
2677
|
+
return {
|
|
2678
|
+
success: true,
|
|
2679
|
+
txIds: allTxIds,
|
|
2680
|
+
confirmedRound: lastConfirmedRound,
|
|
2681
|
+
...totalMatchedQuantity > 0 && { matchedQuantity: totalMatchedQuantity, matchedPrice }
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2551
2684
|
return {
|
|
2552
2685
|
success: true,
|
|
2553
|
-
txIds:
|
|
2554
|
-
confirmedRound:
|
|
2686
|
+
txIds: allTxIds,
|
|
2687
|
+
confirmedRound: lastConfirmedRound
|
|
2555
2688
|
};
|
|
2556
2689
|
};
|
|
2557
2690
|
var splitShares = async (config, params) => {
|
|
@@ -3034,6 +3167,19 @@ var AlphaClient = class {
|
|
|
3034
3167
|
async proposeMatch(params) {
|
|
3035
3168
|
return proposeMatch(this.config, params);
|
|
3036
3169
|
}
|
|
3170
|
+
/**
|
|
3171
|
+
* Matches two existing limit orders (no create-escrow in the group).
|
|
3172
|
+
*
|
|
3173
|
+
* Calls the market app's process_potential_match(maker, taker). Use this
|
|
3174
|
+
* after amending an order: the amended order is the taker (pays the fee),
|
|
3175
|
+
* the counterparty is the maker.
|
|
3176
|
+
*
|
|
3177
|
+
* @param params - Process match params (marketAppId, makerEscrowAppId, takerEscrowAppId)
|
|
3178
|
+
* @returns Whether the match succeeded
|
|
3179
|
+
*/
|
|
3180
|
+
async processMatch(params) {
|
|
3181
|
+
return processMatch(this.config, params);
|
|
3182
|
+
}
|
|
3037
3183
|
/**
|
|
3038
3184
|
* Amends (edits) an existing unfilled order in-place.
|
|
3039
3185
|
*
|
|
@@ -3116,6 +3262,19 @@ var AlphaClient = class {
|
|
|
3116
3262
|
async getOrderbook(marketAppId) {
|
|
3117
3263
|
return getOrderbook(this.config, marketAppId);
|
|
3118
3264
|
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Fetches the full processed orderbook snapshot for a market from the Alpha REST API.
|
|
3267
|
+
*
|
|
3268
|
+
* Returns the same shape as websocket `orderbook_changed.orderbook`: a record keyed by
|
|
3269
|
+
* `marketAppId`, where each value includes aggregated bids/asks plus detailed yes/no orders.
|
|
3270
|
+
* Requires `apiKey`.
|
|
3271
|
+
*
|
|
3272
|
+
* @param marketId - The Alpha market UUID
|
|
3273
|
+
* @returns Full processed market orderbook keyed by marketAppId
|
|
3274
|
+
*/
|
|
3275
|
+
async getFullOrderbookFromApi(marketId) {
|
|
3276
|
+
return getFullOrderbookFromApi(this.config, marketId);
|
|
3277
|
+
}
|
|
3119
3278
|
/**
|
|
3120
3279
|
* Gets open orders for a specific wallet on a market.
|
|
3121
3280
|
*
|
|
@@ -3211,6 +3370,310 @@ var AlphaClient = class {
|
|
|
3211
3370
|
}
|
|
3212
3371
|
};
|
|
3213
3372
|
|
|
3214
|
-
|
|
3373
|
+
// src/websocket.ts
|
|
3374
|
+
var WS_OPEN = 1;
|
|
3375
|
+
var resolveWebSocket = (provided) => {
|
|
3376
|
+
if (provided) return provided;
|
|
3377
|
+
if (typeof globalThis !== "undefined" && globalThis.WebSocket) {
|
|
3378
|
+
return globalThis.WebSocket;
|
|
3379
|
+
}
|
|
3380
|
+
throw new Error(
|
|
3381
|
+
'No WebSocket implementation found. On Node.js < 22, install the "ws" package and pass it: new AlphaWebSocket({ WebSocket: require("ws") })'
|
|
3382
|
+
);
|
|
3383
|
+
};
|
|
3384
|
+
var AlphaWebSocket = class {
|
|
3385
|
+
url;
|
|
3386
|
+
reconnectEnabled;
|
|
3387
|
+
maxReconnectAttempts;
|
|
3388
|
+
heartbeatIntervalMs;
|
|
3389
|
+
WebSocketImpl;
|
|
3390
|
+
ws = null;
|
|
3391
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
3392
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3393
|
+
lastOrderbookVersionBySubscription = /* @__PURE__ */ new Map();
|
|
3394
|
+
heartbeatTimer = null;
|
|
3395
|
+
reconnectTimer = null;
|
|
3396
|
+
reconnectAttempts = 0;
|
|
3397
|
+
intentionallyClosed = false;
|
|
3398
|
+
connectPromise = null;
|
|
3399
|
+
constructor(config) {
|
|
3400
|
+
this.url = config?.url ?? DEFAULT_WSS_BASE_URL;
|
|
3401
|
+
this.reconnectEnabled = config?.reconnect ?? true;
|
|
3402
|
+
this.maxReconnectAttempts = config?.maxReconnectAttempts ?? Infinity;
|
|
3403
|
+
this.heartbeatIntervalMs = config?.heartbeatIntervalMs ?? 6e4;
|
|
3404
|
+
this.WebSocketImpl = resolveWebSocket(config?.WebSocket);
|
|
3405
|
+
}
|
|
3406
|
+
/** Whether the WebSocket is currently open and connected */
|
|
3407
|
+
get connected() {
|
|
3408
|
+
return this.ws?.readyState === WS_OPEN;
|
|
3409
|
+
}
|
|
3410
|
+
// ============================================
|
|
3411
|
+
// Subscribe Methods
|
|
3412
|
+
// ============================================
|
|
3413
|
+
/**
|
|
3414
|
+
* Subscribe to live market probability updates (incremental diffs).
|
|
3415
|
+
* @returns An unsubscribe function
|
|
3416
|
+
*/
|
|
3417
|
+
subscribeLiveMarkets(callback) {
|
|
3418
|
+
return this.subscribe("get-live-markets", {}, "markets_changed", callback);
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Subscribe to change events for a single market.
|
|
3422
|
+
* @param slug - The market slug
|
|
3423
|
+
* @returns An unsubscribe function
|
|
3424
|
+
*/
|
|
3425
|
+
subscribeMarket(slug, callback) {
|
|
3426
|
+
return this.subscribe("get-market", { slug }, "market_changed", callback);
|
|
3427
|
+
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Subscribe to full orderbook snapshots (~5s interval on changes).
|
|
3430
|
+
* @param slug - The market slug
|
|
3431
|
+
* @returns An unsubscribe function
|
|
3432
|
+
*/
|
|
3433
|
+
subscribeOrderbook(slug, callback) {
|
|
3434
|
+
return this.subscribe("get-orderbook", { slug }, "orderbook_changed", callback);
|
|
3435
|
+
}
|
|
3436
|
+
/**
|
|
3437
|
+
* Subscribe to wallet order updates.
|
|
3438
|
+
* @param wallet - The wallet address
|
|
3439
|
+
* @returns An unsubscribe function
|
|
3440
|
+
*/
|
|
3441
|
+
subscribeWalletOrders(wallet, callback) {
|
|
3442
|
+
return this.subscribe("get-wallet-orders", { wallet }, "wallet_orders_changed", callback);
|
|
3443
|
+
}
|
|
3444
|
+
// ============================================
|
|
3445
|
+
// Control Methods
|
|
3446
|
+
// ============================================
|
|
3447
|
+
/** Query the server for the list of active subscriptions on this connection */
|
|
3448
|
+
listSubscriptions() {
|
|
3449
|
+
return this.sendRequest({ method: "LIST_SUBSCRIPTIONS" });
|
|
3450
|
+
}
|
|
3451
|
+
/** Query a server property (e.g. "heartbeat", "limits") */
|
|
3452
|
+
getProperty(property) {
|
|
3453
|
+
return this.sendRequest({ method: "GET_PROPERTY", params: [property] });
|
|
3454
|
+
}
|
|
3455
|
+
// ============================================
|
|
3456
|
+
// Lifecycle
|
|
3457
|
+
// ============================================
|
|
3458
|
+
/** Open the WebSocket connection. Called automatically on first subscribe. */
|
|
3459
|
+
connect() {
|
|
3460
|
+
if (this.connectPromise) return this.connectPromise;
|
|
3461
|
+
this.connectPromise = this.doConnect();
|
|
3462
|
+
return this.connectPromise;
|
|
3463
|
+
}
|
|
3464
|
+
/** Close the connection and clean up all resources */
|
|
3465
|
+
close() {
|
|
3466
|
+
this.intentionallyClosed = true;
|
|
3467
|
+
this.clearTimers();
|
|
3468
|
+
this.subscriptions.clear();
|
|
3469
|
+
for (const [, req] of this.pendingRequests) {
|
|
3470
|
+
clearTimeout(req.timer);
|
|
3471
|
+
req.reject(new Error("WebSocket closed"));
|
|
3472
|
+
}
|
|
3473
|
+
this.pendingRequests.clear();
|
|
3474
|
+
if (this.ws) {
|
|
3475
|
+
this.ws.close();
|
|
3476
|
+
this.ws = null;
|
|
3477
|
+
}
|
|
3478
|
+
this.connectPromise = null;
|
|
3479
|
+
}
|
|
3480
|
+
// ============================================
|
|
3481
|
+
// Internal
|
|
3482
|
+
// ============================================
|
|
3483
|
+
buildStreamKey(stream, params) {
|
|
3484
|
+
const parts = [stream, ...Object.entries(params).sort().map(([k, v]) => `${k}=${v}`)];
|
|
3485
|
+
return parts.join("&");
|
|
3486
|
+
}
|
|
3487
|
+
buildQueryString() {
|
|
3488
|
+
const subs = [...this.subscriptions.values()];
|
|
3489
|
+
if (subs.length === 0) return "";
|
|
3490
|
+
const first = subs[0];
|
|
3491
|
+
const params = new URLSearchParams({ stream: first.stream, ...first.params });
|
|
3492
|
+
return "?" + params.toString();
|
|
3493
|
+
}
|
|
3494
|
+
subscribe(stream, params, eventType, callback) {
|
|
3495
|
+
const key = this.buildStreamKey(stream, params);
|
|
3496
|
+
this.subscriptions.set(key, { stream, params, callback, eventType });
|
|
3497
|
+
if (this.connected) {
|
|
3498
|
+
this.sendSubscribe(stream, params);
|
|
3499
|
+
} else {
|
|
3500
|
+
this.connect();
|
|
3501
|
+
}
|
|
3502
|
+
return () => {
|
|
3503
|
+
this.subscriptions.delete(key);
|
|
3504
|
+
this.lastOrderbookVersionBySubscription.delete(key);
|
|
3505
|
+
if (this.connected) {
|
|
3506
|
+
this.sendUnsubscribe(stream, params);
|
|
3507
|
+
}
|
|
3508
|
+
};
|
|
3509
|
+
}
|
|
3510
|
+
async doConnect() {
|
|
3511
|
+
this.intentionallyClosed = false;
|
|
3512
|
+
return new Promise((resolve, reject) => {
|
|
3513
|
+
const qs = this.buildQueryString();
|
|
3514
|
+
const ws = new this.WebSocketImpl(this.url + qs);
|
|
3515
|
+
ws.onopen = () => {
|
|
3516
|
+
this.ws = ws;
|
|
3517
|
+
this.reconnectAttempts = 0;
|
|
3518
|
+
this.startHeartbeat();
|
|
3519
|
+
const subs = [...this.subscriptions.values()];
|
|
3520
|
+
for (const sub of subs) {
|
|
3521
|
+
this.sendSubscribe(sub.stream, sub.params);
|
|
3522
|
+
}
|
|
3523
|
+
resolve();
|
|
3524
|
+
};
|
|
3525
|
+
ws.onmessage = (event) => {
|
|
3526
|
+
this.handleMessage(event.data);
|
|
3527
|
+
};
|
|
3528
|
+
ws.onclose = () => {
|
|
3529
|
+
this.ws = null;
|
|
3530
|
+
this.connectPromise = null;
|
|
3531
|
+
this.stopHeartbeat();
|
|
3532
|
+
if (!this.intentionallyClosed) {
|
|
3533
|
+
this.scheduleReconnect();
|
|
3534
|
+
}
|
|
3535
|
+
};
|
|
3536
|
+
ws.onerror = (err) => {
|
|
3537
|
+
if (!this.ws) {
|
|
3538
|
+
reject(new Error("WebSocket connection failed"));
|
|
3539
|
+
}
|
|
3540
|
+
};
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
handleMessage(raw) {
|
|
3544
|
+
let msg;
|
|
3545
|
+
try {
|
|
3546
|
+
msg = JSON.parse(raw);
|
|
3547
|
+
} catch {
|
|
3548
|
+
return;
|
|
3549
|
+
}
|
|
3550
|
+
if (msg.type === "ping") {
|
|
3551
|
+
this.send({ method: "PONG" });
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
const responseId = typeof msg.id === "string" ? msg.id : typeof msg.requestId === "string" ? msg.requestId : null;
|
|
3555
|
+
if (responseId && this.pendingRequests.has(responseId)) {
|
|
3556
|
+
const req = this.pendingRequests.get(responseId);
|
|
3557
|
+
this.pendingRequests.delete(responseId);
|
|
3558
|
+
clearTimeout(req.timer);
|
|
3559
|
+
req.resolve(msg);
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
const eventType = msg.type;
|
|
3563
|
+
if (!eventType) return;
|
|
3564
|
+
for (const [key, sub] of this.subscriptions.entries()) {
|
|
3565
|
+
if (!this.matchesSubscriptionMessage(sub, msg)) {
|
|
3566
|
+
continue;
|
|
3567
|
+
}
|
|
3568
|
+
if (!this.shouldDispatchSubscriptionMessage(key, sub, msg)) {
|
|
3569
|
+
continue;
|
|
3570
|
+
}
|
|
3571
|
+
try {
|
|
3572
|
+
sub.callback(msg);
|
|
3573
|
+
} catch {
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3577
|
+
matchesSubscriptionMessage(sub, msg) {
|
|
3578
|
+
if (sub.eventType !== msg.type) return false;
|
|
3579
|
+
if (msg.type === "orderbook_changed") {
|
|
3580
|
+
const messageMarketId = typeof msg.marketId === "string" ? msg.marketId : "";
|
|
3581
|
+
const messageSlug = typeof msg.slug === "string" ? msg.slug : "";
|
|
3582
|
+
const subscriptionMarketId = typeof sub.params.marketId === "string" ? sub.params.marketId : "";
|
|
3583
|
+
const subscriptionSlug = typeof sub.params.slug === "string" ? sub.params.slug : "";
|
|
3584
|
+
if (subscriptionMarketId) return subscriptionMarketId === messageMarketId;
|
|
3585
|
+
if (subscriptionSlug && messageSlug) return subscriptionSlug === messageSlug;
|
|
3586
|
+
if (subscriptionSlug || subscriptionMarketId) return false;
|
|
3587
|
+
}
|
|
3588
|
+
if (msg.type === "wallet_orders_changed") {
|
|
3589
|
+
const messageWallet = typeof msg.wallet === "string" ? msg.wallet : "";
|
|
3590
|
+
const subscriptionWallet = typeof sub.params.wallet === "string" ? sub.params.wallet : "";
|
|
3591
|
+
if (subscriptionWallet) return subscriptionWallet === messageWallet;
|
|
3592
|
+
return false;
|
|
3593
|
+
}
|
|
3594
|
+
return true;
|
|
3595
|
+
}
|
|
3596
|
+
shouldDispatchSubscriptionMessage(key, sub, msg) {
|
|
3597
|
+
if (sub.eventType !== "orderbook_changed") {
|
|
3598
|
+
return true;
|
|
3599
|
+
}
|
|
3600
|
+
const version = Number(msg.version ?? 0);
|
|
3601
|
+
if (!Number.isFinite(version)) {
|
|
3602
|
+
return true;
|
|
3603
|
+
}
|
|
3604
|
+
const lastVersion = this.lastOrderbookVersionBySubscription.get(key) ?? 0;
|
|
3605
|
+
if (version < lastVersion) {
|
|
3606
|
+
return false;
|
|
3607
|
+
}
|
|
3608
|
+
this.lastOrderbookVersionBySubscription.set(key, version);
|
|
3609
|
+
return true;
|
|
3610
|
+
}
|
|
3611
|
+
sendSubscribe(stream, params) {
|
|
3612
|
+
this.send({ method: "SUBSCRIBE", params: [{ stream, ...params }] });
|
|
3613
|
+
}
|
|
3614
|
+
sendUnsubscribe(stream, params) {
|
|
3615
|
+
this.send({ method: "UNSUBSCRIBE", params: [{ stream, ...params }] });
|
|
3616
|
+
}
|
|
3617
|
+
sendRequest(payload, timeoutMs = 1e4) {
|
|
3618
|
+
const requestId = crypto.randomUUID();
|
|
3619
|
+
return new Promise((resolve, reject) => {
|
|
3620
|
+
const timer = setTimeout(() => {
|
|
3621
|
+
this.pendingRequests.delete(requestId);
|
|
3622
|
+
reject(new Error("Request timed out"));
|
|
3623
|
+
}, timeoutMs);
|
|
3624
|
+
this.pendingRequests.set(requestId, { resolve, reject, timer });
|
|
3625
|
+
if (!this.connected) {
|
|
3626
|
+
this.connect().then(() => {
|
|
3627
|
+
this.send({ ...payload, id: requestId });
|
|
3628
|
+
}).catch(reject);
|
|
3629
|
+
} else {
|
|
3630
|
+
this.send({ ...payload, id: requestId });
|
|
3631
|
+
}
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3634
|
+
send(data) {
|
|
3635
|
+
if (this.ws?.readyState === WS_OPEN) {
|
|
3636
|
+
this.ws.send(JSON.stringify(data));
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
// ============================================
|
|
3640
|
+
// Heartbeat
|
|
3641
|
+
// ============================================
|
|
3642
|
+
startHeartbeat() {
|
|
3643
|
+
this.stopHeartbeat();
|
|
3644
|
+
this.heartbeatTimer = setInterval(() => {
|
|
3645
|
+
this.send({ method: "PING" });
|
|
3646
|
+
}, this.heartbeatIntervalMs);
|
|
3647
|
+
}
|
|
3648
|
+
stopHeartbeat() {
|
|
3649
|
+
if (this.heartbeatTimer) {
|
|
3650
|
+
clearInterval(this.heartbeatTimer);
|
|
3651
|
+
this.heartbeatTimer = null;
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
// ============================================
|
|
3655
|
+
// Reconnect
|
|
3656
|
+
// ============================================
|
|
3657
|
+
scheduleReconnect() {
|
|
3658
|
+
if (!this.reconnectEnabled) return;
|
|
3659
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
|
|
3660
|
+
const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
|
|
3661
|
+
this.reconnectAttempts++;
|
|
3662
|
+
this.reconnectTimer = setTimeout(() => {
|
|
3663
|
+
this.reconnectTimer = null;
|
|
3664
|
+
this.connect().catch(() => {
|
|
3665
|
+
});
|
|
3666
|
+
}, delay);
|
|
3667
|
+
}
|
|
3668
|
+
clearTimers() {
|
|
3669
|
+
this.stopHeartbeat();
|
|
3670
|
+
if (this.reconnectTimer) {
|
|
3671
|
+
clearTimeout(this.reconnectTimer);
|
|
3672
|
+
this.reconnectTimer = null;
|
|
3673
|
+
}
|
|
3674
|
+
}
|
|
3675
|
+
};
|
|
3676
|
+
|
|
3677
|
+
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 };
|
|
3215
3678
|
//# sourceMappingURL=index.js.map
|
|
3216
3679
|
//# sourceMappingURL=index.js.map
|