@alango/dr-manhattan 0.1.0 → 0.1.2

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
@@ -183,8 +183,7 @@ await ws.disconnect();
183
183
  #### Limitless WebSocket
184
184
 
185
185
  ```typescript
186
- import { Limitless } from '@alango/dr-manhattan';
187
- import { LimitlessWebSocket } from '@alango/dr-manhattan/exchanges/limitless';
186
+ import { LimitlessWebSocket } from '@alango/dr-manhattan';
188
187
 
189
188
  const ws = new LimitlessWebSocket();
190
189
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import WebSocket from 'ws';
3
3
  import pino from 'pino';
4
+ import { Socket } from 'socket.io-client';
4
5
 
5
6
  /**
6
7
  * Order-related types for prediction market trading.
@@ -611,6 +612,73 @@ declare class Opinion extends Exchange {
611
612
  };
612
613
  }
613
614
 
615
+ interface LimitlessWsConfig {
616
+ verbose?: boolean;
617
+ sessionCookie?: string;
618
+ autoReconnect?: boolean;
619
+ reconnectDelay?: number;
620
+ maxReconnectAttempts?: number;
621
+ }
622
+ interface LimitlessOrderbookUpdate {
623
+ slug: string;
624
+ bids: [number, number][];
625
+ asks: [number, number][];
626
+ timestamp: Date;
627
+ }
628
+ interface LimitlessPriceUpdate {
629
+ marketAddress: string;
630
+ yesPrice: number;
631
+ noPrice: number;
632
+ blockNumber: number;
633
+ timestamp: Date;
634
+ }
635
+ interface LimitlessPositionUpdate {
636
+ account: string;
637
+ marketAddress: string;
638
+ tokenId: string;
639
+ balance: number;
640
+ outcomeIndex: number;
641
+ marketType: 'AMM' | 'CLOB';
642
+ }
643
+ type OrderbookUpdateCallback = (update: LimitlessOrderbookUpdate) => void | Promise<void>;
644
+ type PriceUpdateCallback = (update: LimitlessPriceUpdate) => void | Promise<void>;
645
+ type PositionUpdateCallback = (update: LimitlessPositionUpdate) => void | Promise<void>;
646
+ type ErrorCallback = (error: string) => void;
647
+ declare class LimitlessWebSocket extends EventEmitter {
648
+ protected config: LimitlessWsConfig;
649
+ protected socket: Socket | null;
650
+ protected state: WebSocketState;
651
+ protected reconnectAttempts: number;
652
+ protected subscribedSlugs: string[];
653
+ protected subscribedAddresses: string[];
654
+ protected orderbookCallbacks: OrderbookUpdateCallback[];
655
+ protected priceCallbacks: PriceUpdateCallback[];
656
+ protected positionCallbacks: PositionUpdateCallback[];
657
+ protected errorCallbacks: ErrorCallback[];
658
+ readonly orderbookManager: OrderbookManager;
659
+ protected tokenToSlug: Map<string, string>;
660
+ constructor(config?: LimitlessWsConfig);
661
+ get isConnected(): boolean;
662
+ connect(): Promise<void>;
663
+ disconnect(): Promise<void>;
664
+ protected setupEventHandlers(): void;
665
+ protected parseOrderbookUpdate(data: Record<string, unknown>): LimitlessOrderbookUpdate | null;
666
+ protected parsePriceUpdate(data: Record<string, unknown>): LimitlessPriceUpdate | null;
667
+ protected parsePositionUpdates(data: Record<string, unknown>): LimitlessPositionUpdate[];
668
+ protected resubscribe(): Promise<void>;
669
+ protected sendSubscription(): Promise<void>;
670
+ subscribeMarket(marketSlug: string): Promise<void>;
671
+ subscribeMarketAddress(marketAddress: string): Promise<void>;
672
+ unsubscribeMarket(marketSlug: string): Promise<void>;
673
+ unsubscribeMarketAddress(marketAddress: string): Promise<void>;
674
+ onOrderbook(callback: OrderbookUpdateCallback): this;
675
+ onPrice(callback: PriceUpdateCallback): this;
676
+ onPosition(callback: PositionUpdateCallback): this;
677
+ onError(callback: ErrorCallback): this;
678
+ watchOrderbookByMarket(marketSlug: string, assetIds: string[], callback?: OrderbookCallback): Promise<void>;
679
+ getOrderbookManager(): OrderbookManager;
680
+ }
681
+
614
682
  interface LimitlessConfig extends ExchangeConfig {
615
683
  host?: string;
616
684
  chainId?: number;
@@ -693,4 +761,4 @@ declare function clampPrice(price: number, min?: number, max?: number): number;
693
761
  declare function formatPrice(price: number, decimals?: number): string;
694
762
  declare function formatUsd(amount: number): string;
695
763
 
696
- export { AuthenticationError, Colors, type CreateOrderParams, type DeltaInfo, DrManhattanError, Exchange, type ExchangeCapabilities, type ExchangeConfig, ExchangeError, type FetchMarketsParams, InsufficientFunds, InvalidOrder, Limitless, type Market, MarketNotFound, MarketUtils, NetworkError, Opinion, type Order, OrderBookWebSocket, OrderSide, OrderStatus, OrderUtils, type Orderbook, type OrderbookCallback, OrderbookManager, type OrderbookUpdate, OrderbookUtils, type OutcomeToken, Polymarket, PolymarketWebSocket, type Position, PositionUtils, type PriceLevel, RateLimitError, Strategy, type StrategyConfig, StrategyState, type WebSocketConfig, WebSocketState, calculateDelta, clampPrice, createExchange, createLogger, formatPrice, formatUsd, listExchanges, logger, roundToTickSize };
764
+ export { AuthenticationError, Colors, type CreateOrderParams, type DeltaInfo, DrManhattanError, Exchange, type ExchangeCapabilities, type ExchangeConfig, ExchangeError, type FetchMarketsParams, InsufficientFunds, InvalidOrder, Limitless, LimitlessWebSocket, type Market, MarketNotFound, MarketUtils, NetworkError, Opinion, type Order, OrderBookWebSocket, OrderSide, OrderStatus, OrderUtils, type Orderbook, type OrderbookCallback, OrderbookManager, type OrderbookUpdate, OrderbookUtils, type OutcomeToken, Polymarket, PolymarketWebSocket, type Position, PositionUtils, type PriceLevel, RateLimitError, Strategy, type StrategyConfig, StrategyState, type WebSocketConfig, WebSocketState, calculateDelta, clampPrice, createExchange, createLogger, formatPrice, formatUsd, listExchanges, logger, roundToTickSize };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from 'events';
2
2
  import WebSocket from 'ws';
3
3
  import { Wallet } from 'ethers';
4
- import 'socket.io-client';
4
+ import { io } from 'socket.io-client';
5
5
  import { ClobClient, Side, AssetType } from '@polymarket/clob-client';
6
6
  import pino from 'pino';
7
7
 
@@ -702,6 +702,347 @@ var Strategy = class extends EventEmitter {
702
702
  return this.state;
703
703
  }
704
704
  };
705
+ var WS_URL = "wss://ws.limitless.exchange";
706
+ var NAMESPACE = "/markets";
707
+ var LimitlessWebSocket = class extends EventEmitter {
708
+ config;
709
+ socket = null;
710
+ state = WebSocketState.DISCONNECTED;
711
+ reconnectAttempts = 0;
712
+ subscribedSlugs = [];
713
+ subscribedAddresses = [];
714
+ orderbookCallbacks = [];
715
+ priceCallbacks = [];
716
+ positionCallbacks = [];
717
+ errorCallbacks = [];
718
+ orderbookManager = new OrderbookManager();
719
+ tokenToSlug = /* @__PURE__ */ new Map();
720
+ constructor(config = {}) {
721
+ super();
722
+ this.config = {
723
+ verbose: false,
724
+ autoReconnect: true,
725
+ reconnectDelay: 1e3,
726
+ maxReconnectAttempts: 999,
727
+ ...config
728
+ };
729
+ }
730
+ get isConnected() {
731
+ return this.state === WebSocketState.CONNECTED && !!this.socket?.connected;
732
+ }
733
+ async connect() {
734
+ if (this.state === WebSocketState.CONNECTED) return;
735
+ this.state = WebSocketState.CONNECTING;
736
+ return new Promise((resolve, reject) => {
737
+ const extraHeaders = {};
738
+ if (this.config.sessionCookie) {
739
+ extraHeaders.Cookie = `limitless_session=${this.config.sessionCookie}`;
740
+ }
741
+ this.socket = io(`${WS_URL}${NAMESPACE}`, {
742
+ transports: ["websocket"],
743
+ reconnection: this.config.autoReconnect,
744
+ reconnectionAttempts: this.config.maxReconnectAttempts,
745
+ reconnectionDelay: this.config.reconnectDelay,
746
+ reconnectionDelayMax: 3e4,
747
+ extraHeaders
748
+ });
749
+ this.setupEventHandlers();
750
+ const connectTimeout = setTimeout(() => {
751
+ reject(new Error("Connection timeout"));
752
+ }, 1e4);
753
+ this.socket.on("connect", () => {
754
+ clearTimeout(connectTimeout);
755
+ this.state = WebSocketState.CONNECTED;
756
+ this.reconnectAttempts = 0;
757
+ if (this.config.verbose) {
758
+ console.log("Connected to Limitless WebSocket");
759
+ }
760
+ this.resubscribe().then(resolve).catch(reject);
761
+ });
762
+ this.socket.on("connect_error", (err) => {
763
+ clearTimeout(connectTimeout);
764
+ if (this.state === WebSocketState.CONNECTING) {
765
+ reject(err);
766
+ }
767
+ });
768
+ });
769
+ }
770
+ async disconnect() {
771
+ this.state = WebSocketState.CLOSED;
772
+ if (this.socket) {
773
+ this.socket.disconnect();
774
+ this.socket = null;
775
+ }
776
+ }
777
+ setupEventHandlers() {
778
+ if (!this.socket) return;
779
+ this.socket.on("disconnect", () => {
780
+ this.state = WebSocketState.DISCONNECTED;
781
+ if (this.config.verbose) {
782
+ console.log("Disconnected from Limitless WebSocket");
783
+ }
784
+ });
785
+ this.socket.on("orderbookUpdate", (data) => {
786
+ try {
787
+ const update = this.parseOrderbookUpdate(data);
788
+ if (update) {
789
+ for (const callback of this.orderbookCallbacks) {
790
+ Promise.resolve(callback(update)).catch((e) => {
791
+ if (this.config.verbose) console.error("Orderbook callback error:", e);
792
+ });
793
+ }
794
+ }
795
+ } catch (e) {
796
+ if (this.config.verbose) console.error("Error parsing orderbook update:", e);
797
+ }
798
+ });
799
+ this.socket.on("newPriceData", (data) => {
800
+ try {
801
+ const update = this.parsePriceUpdate(data);
802
+ if (update) {
803
+ for (const callback of this.priceCallbacks) {
804
+ Promise.resolve(callback(update)).catch((e) => {
805
+ if (this.config.verbose) console.error("Price callback error:", e);
806
+ });
807
+ }
808
+ }
809
+ } catch (e) {
810
+ if (this.config.verbose) console.error("Error parsing price update:", e);
811
+ }
812
+ });
813
+ this.socket.on("positions", (data) => {
814
+ try {
815
+ const updates = this.parsePositionUpdates(data);
816
+ for (const update of updates) {
817
+ for (const callback of this.positionCallbacks) {
818
+ Promise.resolve(callback(update)).catch((e) => {
819
+ if (this.config.verbose) console.error("Position callback error:", e);
820
+ });
821
+ }
822
+ }
823
+ } catch (e) {
824
+ if (this.config.verbose) console.error("Error parsing position update:", e);
825
+ }
826
+ });
827
+ this.socket.on("authenticated", () => {
828
+ if (this.config.verbose) {
829
+ console.log("Authenticated with Limitless WebSocket");
830
+ }
831
+ });
832
+ this.socket.on("exception", (data) => {
833
+ const errorMsg = String(data);
834
+ if (this.config.verbose) {
835
+ console.error("WebSocket exception:", errorMsg);
836
+ }
837
+ for (const callback of this.errorCallbacks) {
838
+ callback(errorMsg);
839
+ }
840
+ });
841
+ this.socket.on("system", (data) => {
842
+ if (this.config.verbose) {
843
+ console.log("System message:", data);
844
+ }
845
+ });
846
+ }
847
+ parseOrderbookUpdate(data) {
848
+ const slug = data.marketSlug ?? data.slug ?? "";
849
+ if (!slug) return null;
850
+ const orderbookData = data.orderbook ?? data;
851
+ const bids = [];
852
+ const rawBids = orderbookData.bids;
853
+ if (rawBids) {
854
+ for (const bid of rawBids) {
855
+ const price = Number(bid.price ?? 0);
856
+ const size = Number(bid.size ?? 0);
857
+ if (price > 0) {
858
+ bids.push([price, size]);
859
+ }
860
+ }
861
+ }
862
+ const asks = [];
863
+ const rawAsks = orderbookData.asks;
864
+ if (rawAsks) {
865
+ for (const ask of rawAsks) {
866
+ const price = Number(ask.price ?? 0);
867
+ const size = Number(ask.size ?? 0);
868
+ if (price > 0) {
869
+ asks.push([price, size]);
870
+ }
871
+ }
872
+ }
873
+ bids.sort((a, b) => b[0] - a[0]);
874
+ asks.sort((a, b) => a[0] - b[0]);
875
+ let timestamp;
876
+ const ts = data.timestamp;
877
+ if (typeof ts === "string") {
878
+ timestamp = new Date(ts);
879
+ } else if (typeof ts === "number") {
880
+ timestamp = new Date(ts > 1e12 ? ts : ts * 1e3);
881
+ } else {
882
+ timestamp = /* @__PURE__ */ new Date();
883
+ }
884
+ return { slug, bids, asks, timestamp };
885
+ }
886
+ parsePriceUpdate(data) {
887
+ const marketAddress = data.marketAddress ?? "";
888
+ if (!marketAddress) return null;
889
+ const prices = data.updatedPrices ?? {};
890
+ const yesPrice = Number(prices.yes ?? 0);
891
+ const noPrice = Number(prices.no ?? 0);
892
+ const blockNumber = Number(data.blockNumber ?? 0);
893
+ let timestamp;
894
+ const ts = data.timestamp;
895
+ if (typeof ts === "string") {
896
+ timestamp = new Date(ts);
897
+ } else {
898
+ timestamp = /* @__PURE__ */ new Date();
899
+ }
900
+ return { marketAddress, yesPrice, noPrice, blockNumber, timestamp };
901
+ }
902
+ parsePositionUpdates(data) {
903
+ const updates = [];
904
+ const account = data.account ?? "";
905
+ const marketAddress = data.marketAddress ?? "";
906
+ const marketType = data.type ?? "CLOB";
907
+ const positions = data.positions ?? [];
908
+ for (const pos of positions) {
909
+ updates.push({
910
+ account,
911
+ marketAddress,
912
+ tokenId: String(pos.tokenId ?? ""),
913
+ balance: Number(pos.balance ?? 0),
914
+ outcomeIndex: Number(pos.outcomeIndex ?? 0),
915
+ marketType
916
+ });
917
+ }
918
+ return updates;
919
+ }
920
+ async resubscribe() {
921
+ if (this.subscribedSlugs.length > 0 || this.subscribedAddresses.length > 0) {
922
+ await this.sendSubscription();
923
+ }
924
+ }
925
+ async sendSubscription() {
926
+ if (!this.socket?.connected) return;
927
+ const payload = {};
928
+ if (this.subscribedAddresses.length > 0) {
929
+ payload.marketAddresses = this.subscribedAddresses;
930
+ }
931
+ if (this.subscribedSlugs.length > 0) {
932
+ payload.marketSlugs = this.subscribedSlugs;
933
+ }
934
+ if (Object.keys(payload).length > 0) {
935
+ this.socket.emit("subscribe_market_prices", payload);
936
+ if (this.config.verbose) {
937
+ console.log("Subscribed to markets:", payload);
938
+ }
939
+ }
940
+ }
941
+ async subscribeMarket(marketSlug) {
942
+ if (!this.subscribedSlugs.includes(marketSlug)) {
943
+ this.subscribedSlugs.push(marketSlug);
944
+ }
945
+ if (this.isConnected) {
946
+ await this.sendSubscription();
947
+ }
948
+ }
949
+ async subscribeMarketAddress(marketAddress) {
950
+ if (!this.subscribedAddresses.includes(marketAddress)) {
951
+ this.subscribedAddresses.push(marketAddress);
952
+ }
953
+ if (this.isConnected) {
954
+ await this.sendSubscription();
955
+ }
956
+ }
957
+ async unsubscribeMarket(marketSlug) {
958
+ const index = this.subscribedSlugs.indexOf(marketSlug);
959
+ if (index !== -1) {
960
+ this.subscribedSlugs.splice(index, 1);
961
+ }
962
+ if (this.isConnected) {
963
+ await this.sendSubscription();
964
+ }
965
+ }
966
+ async unsubscribeMarketAddress(marketAddress) {
967
+ const index = this.subscribedAddresses.indexOf(marketAddress);
968
+ if (index !== -1) {
969
+ this.subscribedAddresses.splice(index, 1);
970
+ }
971
+ if (this.isConnected) {
972
+ await this.sendSubscription();
973
+ }
974
+ }
975
+ onOrderbook(callback) {
976
+ this.orderbookCallbacks.push(callback);
977
+ return this;
978
+ }
979
+ onPrice(callback) {
980
+ this.priceCallbacks.push(callback);
981
+ return this;
982
+ }
983
+ onPosition(callback) {
984
+ this.positionCallbacks.push(callback);
985
+ return this;
986
+ }
987
+ onError(callback) {
988
+ this.errorCallbacks.push(callback);
989
+ return this;
990
+ }
991
+ async watchOrderbookByMarket(marketSlug, assetIds, callback) {
992
+ for (const assetId of assetIds) {
993
+ this.tokenToSlug.set(assetId, marketSlug);
994
+ }
995
+ const yesToken = assetIds[0];
996
+ const noToken = assetIds[1];
997
+ const orderbookHandler = (update) => {
998
+ const ts = update.timestamp.getTime();
999
+ if (yesToken) {
1000
+ this.orderbookManager.update(yesToken, {
1001
+ bids: update.bids,
1002
+ asks: update.asks,
1003
+ timestamp: ts,
1004
+ assetId: yesToken,
1005
+ marketId: update.slug
1006
+ });
1007
+ }
1008
+ if (noToken) {
1009
+ const noBids = update.asks.map(([price, size]) => [
1010
+ Math.round((1 - price) * 1e3) / 1e3,
1011
+ size
1012
+ ]);
1013
+ const noAsks = update.bids.map(([price, size]) => [
1014
+ Math.round((1 - price) * 1e3) / 1e3,
1015
+ size
1016
+ ]);
1017
+ noBids.sort((a, b) => b[0] - a[0]);
1018
+ noAsks.sort((a, b) => a[0] - b[0]);
1019
+ this.orderbookManager.update(noToken, {
1020
+ bids: noBids,
1021
+ asks: noAsks,
1022
+ timestamp: ts,
1023
+ assetId: noToken,
1024
+ marketId: update.slug
1025
+ });
1026
+ }
1027
+ if (callback) {
1028
+ callback(marketSlug, {
1029
+ marketId: marketSlug,
1030
+ bids: update.bids,
1031
+ asks: update.asks,
1032
+ timestamp: ts
1033
+ });
1034
+ }
1035
+ };
1036
+ this.onOrderbook(orderbookHandler);
1037
+ if (!this.isConnected) {
1038
+ await this.connect();
1039
+ }
1040
+ await this.subscribeMarket(marketSlug);
1041
+ }
1042
+ getOrderbookManager() {
1043
+ return this.orderbookManager;
1044
+ }
1045
+ };
705
1046
 
706
1047
  // src/exchanges/limitless/index.ts
707
1048
  var BASE_URL = "https://api.limitless.exchange";
@@ -2392,9 +2733,9 @@ var Polymarket = class extends Exchange {
2392
2733
  };
2393
2734
 
2394
2735
  // src/exchanges/polymarket/polymarket-ws.ts
2395
- var WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
2736
+ var WS_URL2 = "wss://ws-subscriptions-clob.polymarket.com/ws/market";
2396
2737
  var PolymarketWebSocket = class extends OrderBookWebSocket {
2397
- wsUrl = WS_URL;
2738
+ wsUrl = WS_URL2;
2398
2739
  apiKey;
2399
2740
  assetSubscriptions = /* @__PURE__ */ new Map();
2400
2741
  constructor(config = {}) {
@@ -2532,6 +2873,6 @@ function formatUsd(amount) {
2532
2873
  return `$${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
2533
2874
  }
2534
2875
 
2535
- export { AuthenticationError, Colors, DrManhattanError, Exchange, ExchangeError, InsufficientFunds, InvalidOrder, Limitless, MarketNotFound, MarketUtils, NetworkError, Opinion, OrderBookWebSocket, OrderSide, OrderStatus, OrderUtils, OrderbookManager, OrderbookUtils, Polymarket, PolymarketWebSocket, PositionUtils, RateLimitError, Strategy, StrategyState, WebSocketState, calculateDelta, clampPrice, createExchange, createLogger, formatPrice, formatUsd, listExchanges, logger, roundToTickSize };
2876
+ export { AuthenticationError, Colors, DrManhattanError, Exchange, ExchangeError, InsufficientFunds, InvalidOrder, Limitless, LimitlessWebSocket, MarketNotFound, MarketUtils, NetworkError, Opinion, OrderBookWebSocket, OrderSide, OrderStatus, OrderUtils, OrderbookManager, OrderbookUtils, Polymarket, PolymarketWebSocket, PositionUtils, RateLimitError, Strategy, StrategyState, WebSocketState, calculateDelta, clampPrice, createExchange, createLogger, formatPrice, formatUsd, listExchanges, logger, roundToTickSize };
2536
2877
  //# sourceMappingURL=index.js.map
2537
2878
  //# sourceMappingURL=index.js.map