@stryke-xyz/premarket-sdk 1.0.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.
Files changed (189) hide show
  1. package/dist/abi/AggregationRouterV6.abi.json +1186 -0
  2. package/dist/abi/NativeOrderFactory.abi.json +291 -0
  3. package/dist/abi/NativeOrderImpl.abi.json +381 -0
  4. package/dist/abi/limitOrderProtocol.json +63 -0
  5. package/dist/address.d.ts +13 -0
  6. package/dist/address.js +32 -0
  7. package/dist/api/filler.d.ts +109 -0
  8. package/dist/api/filler.js +289 -0
  9. package/dist/api/index.d.ts +3 -0
  10. package/dist/api/index.js +3 -0
  11. package/dist/api/order-helper.d.ts +59 -0
  12. package/dist/api/order-helper.js +112 -0
  13. package/dist/api/orderbook-api.d.ts +36 -0
  14. package/dist/api/orderbook-api.js +69 -0
  15. package/dist/bps.d.ts +35 -0
  16. package/dist/bps.js +52 -0
  17. package/dist/config/chains.d.ts +50 -0
  18. package/dist/config/chains.js +21 -0
  19. package/dist/config/index.d.ts +25 -0
  20. package/dist/config/index.js +103 -0
  21. package/dist/config/markets.d.ts +53 -0
  22. package/dist/config/markets.js +125 -0
  23. package/dist/constants.d.ts +4 -0
  24. package/dist/constants.js +30 -0
  25. package/dist/index.d.ts +19 -0
  26. package/dist/index.js +21 -0
  27. package/dist/limit-order/amounts.d.ts +14 -0
  28. package/dist/limit-order/amounts.js +19 -0
  29. package/dist/limit-order/eip712/domain.d.ts +10 -0
  30. package/dist/limit-order/eip712/domain.js +18 -0
  31. package/dist/limit-order/eip712/eip712.types.d.ts +23 -0
  32. package/dist/limit-order/eip712/eip712.types.js +1 -0
  33. package/dist/limit-order/eip712/index.d.ts +3 -0
  34. package/dist/limit-order/eip712/index.js +3 -0
  35. package/dist/limit-order/eip712/order-typed-data-builder.d.ts +6 -0
  36. package/dist/limit-order/eip712/order-typed-data-builder.js +25 -0
  37. package/dist/limit-order/extensions/extension-builder.d.ts +35 -0
  38. package/dist/limit-order/extensions/extension-builder.js +83 -0
  39. package/dist/limit-order/extensions/extension.d.ts +45 -0
  40. package/dist/limit-order/extensions/extension.js +108 -0
  41. package/dist/limit-order/extensions/extension.spec.d.ts +1 -0
  42. package/dist/limit-order/extensions/extension.spec.js +17 -0
  43. package/dist/limit-order/extensions/fee-taker/errors.d.ts +2 -0
  44. package/dist/limit-order/extensions/fee-taker/errors.js +2 -0
  45. package/dist/limit-order/extensions/fee-taker/fee-calculator.d.ts +40 -0
  46. package/dist/limit-order/extensions/fee-taker/fee-calculator.js +83 -0
  47. package/dist/limit-order/extensions/fee-taker/fee-calculator.spec.d.ts +1 -0
  48. package/dist/limit-order/extensions/fee-taker/fee-calculator.spec.js +14 -0
  49. package/dist/limit-order/extensions/fee-taker/fee-taker.extension.d.ts +130 -0
  50. package/dist/limit-order/extensions/fee-taker/fee-taker.extension.js +249 -0
  51. package/dist/limit-order/extensions/fee-taker/fee-taker.extension.spec.d.ts +1 -0
  52. package/dist/limit-order/extensions/fee-taker/fee-taker.extension.spec.js +72 -0
  53. package/dist/limit-order/extensions/fee-taker/fees.d.ts +19 -0
  54. package/dist/limit-order/extensions/fee-taker/fees.js +36 -0
  55. package/dist/limit-order/extensions/fee-taker/index.d.ts +7 -0
  56. package/dist/limit-order/extensions/fee-taker/index.js +7 -0
  57. package/dist/limit-order/extensions/fee-taker/integrator-fee.d.ts +13 -0
  58. package/dist/limit-order/extensions/fee-taker/integrator-fee.js +28 -0
  59. package/dist/limit-order/extensions/fee-taker/resolver-fee.d.ts +19 -0
  60. package/dist/limit-order/extensions/fee-taker/resolver-fee.js +28 -0
  61. package/dist/limit-order/extensions/fee-taker/types.d.ts +4 -0
  62. package/dist/limit-order/extensions/fee-taker/types.js +1 -0
  63. package/dist/limit-order/extensions/fee-taker/whitelist-half-address.d.ts +18 -0
  64. package/dist/limit-order/extensions/fee-taker/whitelist-half-address.js +26 -0
  65. package/dist/limit-order/extensions/index.d.ts +3 -0
  66. package/dist/limit-order/extensions/index.js +3 -0
  67. package/dist/limit-order/index.d.ts +10 -0
  68. package/dist/limit-order/index.js +10 -0
  69. package/dist/limit-order/interaction.d.ts +16 -0
  70. package/dist/limit-order/interaction.js +25 -0
  71. package/dist/limit-order/interaction.spec.d.ts +1 -0
  72. package/dist/limit-order/interaction.spec.js +8 -0
  73. package/dist/limit-order/limit-order-with-fee.d.ts +59 -0
  74. package/dist/limit-order/limit-order-with-fee.js +94 -0
  75. package/dist/limit-order/limit-order-with-fee.spec.d.ts +1 -0
  76. package/dist/limit-order/limit-order-with-fee.spec.js +31 -0
  77. package/dist/limit-order/limit-order.d.ts +63 -0
  78. package/dist/limit-order/limit-order.js +211 -0
  79. package/dist/limit-order/limit-order.spec.d.ts +1 -0
  80. package/dist/limit-order/limit-order.spec.js +103 -0
  81. package/dist/limit-order/maker-traits.d.ts +200 -0
  82. package/dist/limit-order/maker-traits.js +309 -0
  83. package/dist/limit-order/maker-traits.spec.d.ts +1 -0
  84. package/dist/limit-order/maker-traits.spec.js +102 -0
  85. package/dist/limit-order/source-track.d.ts +1 -0
  86. package/dist/limit-order/source-track.js +22 -0
  87. package/dist/limit-order/taker-traits.d.ts +141 -0
  88. package/dist/limit-order/taker-traits.js +207 -0
  89. package/dist/limit-order/types.d.ts +24 -0
  90. package/dist/limit-order/types.js +1 -0
  91. package/dist/limit-order/verification.d.ts +16 -0
  92. package/dist/limit-order/verification.js +108 -0
  93. package/dist/limit-order-contract/index.d.ts +4 -0
  94. package/dist/limit-order-contract/index.js +4 -0
  95. package/dist/limit-order-contract/limit-order-contract.d.ts +34 -0
  96. package/dist/limit-order-contract/limit-order-contract.js +79 -0
  97. package/dist/limit-order-contract/native-order-factory.d.ts +10 -0
  98. package/dist/limit-order-contract/native-order-factory.js +22 -0
  99. package/dist/limit-order-contract/native-order-impl.d.ts +10 -0
  100. package/dist/limit-order-contract/native-order-impl.js +24 -0
  101. package/dist/limit-order-contract/proxy-factory.d.ts +20 -0
  102. package/dist/limit-order-contract/proxy-factory.js +32 -0
  103. package/dist/limit-order-contract/proxy-factory.spec.d.ts +1 -0
  104. package/dist/limit-order-contract/proxy-factory.spec.js +16 -0
  105. package/dist/limit-order-contract/types.d.ts +6 -0
  106. package/dist/limit-order-contract/types.js +1 -0
  107. package/dist/ponder/client/index.d.ts +23 -0
  108. package/dist/ponder/client/index.js +44 -0
  109. package/dist/ponder/client/queries/markets.d.ts +30 -0
  110. package/dist/ponder/client/queries/markets.js +200 -0
  111. package/dist/ponder/client/queries/positions.d.ts +13 -0
  112. package/dist/ponder/client/queries/positions.js +406 -0
  113. package/dist/ponder/client/types/history.d.ts +94 -0
  114. package/dist/ponder/client/types/history.js +1 -0
  115. package/dist/ponder/client/types/index.d.ts +5 -0
  116. package/dist/ponder/client/types/index.js +5 -0
  117. package/dist/ponder/client/types/market.d.ts +45 -0
  118. package/dist/ponder/client/types/market.js +1 -0
  119. package/dist/ponder/client/types/position.d.ts +32 -0
  120. package/dist/ponder/client/types/position.js +1 -0
  121. package/dist/ponder/client/types/serializers.d.ts +57 -0
  122. package/dist/ponder/client/types/serializers.js +248 -0
  123. package/dist/ponder/client/types/user.d.ts +5 -0
  124. package/dist/ponder/client/types/user.js +1 -0
  125. package/dist/ponder/client/utils.d.ts +1 -0
  126. package/dist/ponder/client/utils.js +32 -0
  127. package/dist/ponder/generated/index.d.ts +18 -0
  128. package/dist/ponder/generated/index.js +20 -0
  129. package/dist/ponder/generated/runtime/batcher.d.ts +105 -0
  130. package/dist/ponder/generated/runtime/batcher.js +188 -0
  131. package/dist/ponder/generated/runtime/createClient.d.ts +17 -0
  132. package/dist/ponder/generated/runtime/createClient.js +24 -0
  133. package/dist/ponder/generated/runtime/error.d.ts +18 -0
  134. package/dist/ponder/generated/runtime/error.js +15 -0
  135. package/dist/ponder/generated/runtime/fetcher.d.ts +10 -0
  136. package/dist/ponder/generated/runtime/fetcher.js +67 -0
  137. package/dist/ponder/generated/runtime/generateGraphqlOperation.d.ts +30 -0
  138. package/dist/ponder/generated/runtime/generateGraphqlOperation.js +128 -0
  139. package/dist/ponder/generated/runtime/index.d.ts +11 -0
  140. package/dist/ponder/generated/runtime/index.js +10 -0
  141. package/dist/ponder/generated/runtime/linkTypeMap.d.ts +9 -0
  142. package/dist/ponder/generated/runtime/linkTypeMap.js +83 -0
  143. package/dist/ponder/generated/runtime/typeSelection.d.ts +28 -0
  144. package/dist/ponder/generated/runtime/typeSelection.js +3 -0
  145. package/dist/ponder/generated/runtime/types.d.ts +55 -0
  146. package/dist/ponder/generated/runtime/types.js +2 -0
  147. package/dist/ponder/generated/schema.d.ts +3026 -0
  148. package/dist/ponder/generated/schema.js +222 -0
  149. package/dist/ponder/generated/types.d.ts +2393 -0
  150. package/dist/ponder/generated/types.js +6915 -0
  151. package/dist/ponder/index.d.ts +2 -0
  152. package/dist/ponder/index.js +2 -0
  153. package/dist/ponder/types.d.ts +3 -0
  154. package/dist/ponder/types.js +1 -0
  155. package/dist/rfq-order/index.d.ts +1 -0
  156. package/dist/rfq-order/index.js +1 -0
  157. package/dist/rfq-order/rfq-order.d.ts +21 -0
  158. package/dist/rfq-order/rfq-order.js +22 -0
  159. package/dist/rfq-order/rfq-order.spec.d.ts +1 -0
  160. package/dist/rfq-order/rfq-order.spec.js +27 -0
  161. package/dist/shared/index.d.ts +2 -0
  162. package/dist/shared/index.js +2 -0
  163. package/dist/shared/types.d.ts +79 -0
  164. package/dist/shared/types.js +12 -0
  165. package/dist/shared/utils.d.ts +7 -0
  166. package/dist/shared/utils.js +19 -0
  167. package/dist/sync/clients/balance-client.d.ts +41 -0
  168. package/dist/sync/clients/balance-client.js +139 -0
  169. package/dist/sync/clients/base-client.d.ts +47 -0
  170. package/dist/sync/clients/base-client.js +154 -0
  171. package/dist/sync/clients/order-client.d.ts +18 -0
  172. package/dist/sync/clients/order-client.js +151 -0
  173. package/dist/sync/index.d.ts +5 -0
  174. package/dist/sync/index.js +3 -0
  175. package/dist/sync/redis-ws-client.d.ts +18 -0
  176. package/dist/sync/redis-ws-client.js +88 -0
  177. package/dist/sync/types.d.ts +20 -0
  178. package/dist/sync/types.js +1 -0
  179. package/dist/utils/mul-div.d.ts +5 -0
  180. package/dist/utils/mul-div.js +13 -0
  181. package/dist/utils/orderUtils.d.ts +19 -0
  182. package/dist/utils/orderUtils.js +51 -0
  183. package/dist/utils/rand-bigint.d.ts +1 -0
  184. package/dist/utils/rand-bigint.js +13 -0
  185. package/dist/utils/rand-bigint.spec.d.ts +1 -0
  186. package/dist/utils/rand-bigint.spec.js +11 -0
  187. package/dist/validations.d.ts +1 -0
  188. package/dist/validations.js +3 -0
  189. package/package.json +60 -0
@@ -0,0 +1,2 @@
1
+ export * from "./client/index.js";
2
+ export * from "./types.js";
@@ -0,0 +1,2 @@
1
+ export * from "./client/index.js";
2
+ export * from "./types.js";
@@ -0,0 +1,3 @@
1
+ export interface PonderClientConfig {
2
+ graphqlUrl: string;
3
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export * from './rfq-order.js';
@@ -0,0 +1 @@
1
+ export * from './rfq-order.js';
@@ -0,0 +1,21 @@
1
+ import { LimitOrder, OrderInfoData } from '../limit-order/index.js';
2
+ import { Address } from '../address.js';
3
+ /**
4
+ * Light, gas efficient version of LimitOrder
5
+ * It does not support multiple fills and extension
6
+ */
7
+ export declare class RfqOrder extends LimitOrder {
8
+ constructor(orderInfo: Omit<OrderInfoData, 'salt' | 'receiver'>, options: {
9
+ allowedSender?: Address;
10
+ /**
11
+ * Timestamp in seconds
12
+ */
13
+ expiration: bigint;
14
+ /**
15
+ * Unique id among all maker orders
16
+ * Max value is UINT_40_MAX
17
+ */
18
+ nonce: bigint;
19
+ usePermit2?: boolean;
20
+ });
21
+ }
@@ -0,0 +1,22 @@
1
+ import { LimitOrder, MakerTraits } from '../limit-order/index.js';
2
+ /**
3
+ * Light, gas efficient version of LimitOrder
4
+ * It does not support multiple fills and extension
5
+ */
6
+ export class RfqOrder extends LimitOrder {
7
+ constructor(orderInfo, options) {
8
+ const { allowedSender, nonce, expiration, usePermit2 } = options;
9
+ const makerTraits = new MakerTraits(0n)
10
+ .disableMultipleFills()
11
+ .allowPartialFills()
12
+ .withExpiration(expiration)
13
+ .withNonce(nonce);
14
+ if (allowedSender) {
15
+ makerTraits.withAllowedSender(allowedSender);
16
+ }
17
+ if (usePermit2) {
18
+ makerTraits.enablePermit2();
19
+ }
20
+ super(orderInfo, makerTraits);
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { RfqOrder } from './rfq-order.js';
2
+ import { Address } from '../address.js';
3
+ describe('RfqOrder', () => {
4
+ it('Should validate max nonce', () => {
5
+ expect(() => new RfqOrder({
6
+ makerAsset: new Address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
7
+ takerAsset: new Address('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
8
+ makingAmount: 1000000000000000000n,
9
+ takingAmount: 1420000000n,
10
+ maker: new Address('0x00000000219ab540356cbb839cbe05303d7705fa')
11
+ }, { nonce: 1n << 41n, expiration: 1000n })).toThrow();
12
+ });
13
+ });
14
+ it('should create RfqOrder with permit2', () => {
15
+ const order = new RfqOrder({
16
+ makerAsset: new Address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'),
17
+ takerAsset: new Address('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
18
+ makingAmount: 1000000000000000000n,
19
+ takingAmount: 1420000000n,
20
+ maker: new Address('0x00000000219ab540356cbb839cbe05303d7705fa')
21
+ }, {
22
+ nonce: 1n,
23
+ expiration: 1000n,
24
+ usePermit2: true
25
+ });
26
+ expect(order.makerTraits.isPermit2()).toEqual(true);
27
+ });
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export { calculateOptionTokenId } from "./utils.js";
@@ -0,0 +1,2 @@
1
+ export * from "./types.js";
2
+ export { calculateOptionTokenId } from "./utils.js";
@@ -0,0 +1,79 @@
1
+ export interface Order {
2
+ salt: string;
3
+ maker: string;
4
+ receiver: string;
5
+ makerAsset: string;
6
+ takerAsset: string;
7
+ makingAmount: string;
8
+ takingAmount: string;
9
+ makerTraits: string;
10
+ }
11
+ export interface OrderSignature {
12
+ r: string;
13
+ vs: string;
14
+ }
15
+ export interface Option {
16
+ marketId: string;
17
+ strikeLowerLimit: string;
18
+ strikeUpperLimit: string;
19
+ isPut: boolean;
20
+ }
21
+ export declare enum OrderType {
22
+ SELL_OPTIONS = "SELL_OPTIONS",
23
+ BUY_OPTIONS = "BUY_OPTIONS"
24
+ }
25
+ export declare enum OrderStatus {
26
+ ACTIVE = "ACTIVE",
27
+ FILLED = "FILLED",
28
+ CANCELLED = "CANCELLED",
29
+ EXPIRED = "EXPIRED"
30
+ }
31
+ export interface StoredOrder {
32
+ id: string;
33
+ orderHash: string;
34
+ orderType: OrderType;
35
+ order: Order;
36
+ extensionEncoded: string;
37
+ signature: OrderSignature;
38
+ marketId: string;
39
+ optionTokenId: string;
40
+ stableToken: string;
41
+ optionAmount: string;
42
+ stableAmount: string;
43
+ status: OrderStatus;
44
+ filledAmount: string;
45
+ remainingMakerAmount: string;
46
+ maker: string;
47
+ operator?: string;
48
+ createdAt: number;
49
+ expiresAt?: number;
50
+ }
51
+ export interface CreateOrderParams {
52
+ orderType: OrderType;
53
+ marketId: string;
54
+ option: Option;
55
+ order: Order;
56
+ extensionEncoded: string;
57
+ signature: OrderSignature;
58
+ optionAmount: string;
59
+ stableAmount: string;
60
+ stableToken: string;
61
+ makerProxyAddress: string;
62
+ operator?: string;
63
+ expiresAt?: number;
64
+ }
65
+ export type CreateOrderRequest = CreateOrderParams;
66
+ export interface OrderQueryParams {
67
+ marketId?: string;
68
+ orderType?: OrderType;
69
+ maker?: string;
70
+ stableToken?: string;
71
+ status?: OrderStatus;
72
+ limit?: number;
73
+ offset?: number;
74
+ }
75
+ export interface OrderResponse {
76
+ success: boolean;
77
+ data?: any;
78
+ error?: string;
79
+ }
@@ -0,0 +1,12 @@
1
+ export var OrderType;
2
+ (function (OrderType) {
3
+ OrderType["SELL_OPTIONS"] = "SELL_OPTIONS";
4
+ OrderType["BUY_OPTIONS"] = "BUY_OPTIONS";
5
+ })(OrderType || (OrderType = {}));
6
+ export var OrderStatus;
7
+ (function (OrderStatus) {
8
+ OrderStatus["ACTIVE"] = "ACTIVE";
9
+ OrderStatus["FILLED"] = "FILLED";
10
+ OrderStatus["CANCELLED"] = "CANCELLED";
11
+ OrderStatus["EXPIRED"] = "EXPIRED";
12
+ })(OrderStatus || (OrderStatus = {}));
@@ -0,0 +1,7 @@
1
+ import { type Hex } from "viem";
2
+ import type { Option } from "./types.js";
3
+ /**
4
+ * Calculate option token ID from option parameters
5
+ * This should match the getTokenId function in OptionTokenFactory
6
+ */
7
+ export declare function calculateOptionTokenId(option: Option): Hex;
@@ -0,0 +1,19 @@
1
+ import { encodeAbiParameters, keccak256 } from "viem";
2
+ /**
3
+ * Calculate option token ID from option parameters
4
+ * This should match the getTokenId function in OptionTokenFactory
5
+ */
6
+ export function calculateOptionTokenId(option) {
7
+ const encoded = encodeAbiParameters([
8
+ { name: "marketId", type: "uint256" },
9
+ { name: "strikeLowerLimit", type: "uint256" },
10
+ { name: "strikeUpperLimit", type: "uint256" },
11
+ { name: "isPut", type: "bool" },
12
+ ], [
13
+ BigInt(option.marketId),
14
+ BigInt(option.strikeLowerLimit),
15
+ BigInt(option.strikeUpperLimit),
16
+ option.isPut,
17
+ ]);
18
+ return keccak256(encoded);
19
+ }
@@ -0,0 +1,41 @@
1
+ import { BaseSyncClient } from "./base-client.js";
2
+ export interface BalanceData {
3
+ maker: string;
4
+ token: string;
5
+ usableBalance: string;
6
+ tokenId?: string;
7
+ }
8
+ export interface BalanceUpdateMessage {
9
+ type: "balanceUpdate";
10
+ seq: number;
11
+ marketId: string;
12
+ balances: BalanceData[];
13
+ timestamp: number;
14
+ }
15
+ export interface BalanceSyncClientConfig {
16
+ redisUrl: string;
17
+ marketId: string;
18
+ channel: string;
19
+ gapRecoveryUrl?: string;
20
+ snapshotUrl?: string;
21
+ }
22
+ export declare class BalanceSyncClient extends BaseSyncClient<BalanceUpdateMessage, BalanceData[], BalanceData, BalanceSyncClientConfig> {
23
+ private balanceListeners;
24
+ constructor(config: Omit<BalanceSyncClientConfig, "channel">);
25
+ protected fetchSnapshot(): Promise<void>;
26
+ protected applyMessage(message: BalanceUpdateMessage): void;
27
+ protected recoverGap(fromSeq: number, toSeq: number): Promise<BalanceUpdateMessage[]>;
28
+ /**
29
+ * Get usable balance for a maker/token pair (or maker/token/tokenId for ERC6909)
30
+ */
31
+ getUsableBalance(maker: string, token: string, tokenId?: string | bigint): string | undefined;
32
+ /**
33
+ * Get all balances as a map of key -> balance string
34
+ */
35
+ getBalanceMap(): Map<string, string>;
36
+ /**
37
+ * Get all balances as BalanceData array
38
+ */
39
+ getBalances(): BalanceData[];
40
+ onBalanceUpdate(callback: (balances: BalanceData[]) => void): () => void;
41
+ }
@@ -0,0 +1,139 @@
1
+ import { BaseSyncClient } from "./base-client.js";
2
+ export class BalanceSyncClient extends BaseSyncClient {
3
+ constructor(config) {
4
+ // Build full config with channel
5
+ const fullConfig = {
6
+ ...config,
7
+ channel: `orderbook:market:${config.marketId}:balances`,
8
+ };
9
+ super(fullConfig);
10
+ this.balanceListeners = new Set();
11
+ }
12
+ async fetchSnapshot() {
13
+ try {
14
+ // Use snapshotUrl if available, otherwise fall back to gapRecoveryUrl
15
+ const baseUrl = this.config.snapshotUrl || this.config.gapRecoveryUrl;
16
+ if (!baseUrl) {
17
+ // If no URL, start from 0
18
+ this.lastSeq = 0;
19
+ return;
20
+ }
21
+ const url = `${baseUrl}/api/sync/balance-snapshot?marketId=${this.config.marketId}`;
22
+ const response = await fetch(url);
23
+ if (!response.ok) {
24
+ throw new Error(`Balance snapshot fetch failed: ${response.status} ${response.statusText}`);
25
+ }
26
+ const data = (await response.json());
27
+ if (data.success && data.data) {
28
+ const balances = data.data.balances || [];
29
+ const snapshotSeq = data.data.seq || 0;
30
+ // Clear and populate balance map
31
+ this.dataMap.clear();
32
+ for (const balance of balances) {
33
+ const key = balance.tokenId
34
+ ? `${balance.maker.toLowerCase()}:${balance.token.toLowerCase()}:${balance.tokenId}`
35
+ : `${balance.maker.toLowerCase()}:${balance.token.toLowerCase()}`;
36
+ this.dataMap.set(key, balance);
37
+ }
38
+ this.lastSeq = snapshotSeq;
39
+ // Notify snapshot listeners
40
+ this.notifySnapshotListeners(balances);
41
+ }
42
+ }
43
+ catch (error) {
44
+ console.error("Error fetching balance snapshot:", error);
45
+ // Fallback to starting from 0
46
+ this.lastSeq = 0;
47
+ }
48
+ }
49
+ applyMessage(message) {
50
+ // Update balance map
51
+ for (const balance of message.balances) {
52
+ // For ERC6909 tokens, include tokenId in the key
53
+ const key = balance.tokenId
54
+ ? `${balance.maker.toLowerCase()}:${balance.token.toLowerCase()}:${balance.tokenId}`
55
+ : `${balance.maker.toLowerCase()}:${balance.token.toLowerCase()}`;
56
+ this.dataMap.set(key, balance);
57
+ }
58
+ // Notify change listeners (from base class)
59
+ this.changeListeners.forEach((listener) => {
60
+ try {
61
+ listener(message.balances);
62
+ }
63
+ catch (error) {
64
+ console.error("Error in change listener:", error);
65
+ }
66
+ });
67
+ // Notify balance-specific listeners
68
+ this.balanceListeners.forEach((listener) => {
69
+ try {
70
+ listener(message.balances);
71
+ }
72
+ catch (error) {
73
+ console.error("Error in balance listener:", error);
74
+ }
75
+ });
76
+ }
77
+ async recoverGap(fromSeq, toSeq) {
78
+ if (!this.config.gapRecoveryUrl) {
79
+ return [];
80
+ }
81
+ const gapSize = toSeq - fromSeq + 1;
82
+ if (gapSize > 1000) {
83
+ await this.fullResync();
84
+ return [];
85
+ }
86
+ this.setStatus("recovering");
87
+ try {
88
+ const url = `${this.config.gapRecoveryUrl}/api/sync/balance-messages?marketId=${this.config.marketId}&fromSeq=${fromSeq}&toSeq=${toSeq}`;
89
+ const response = await fetch(url);
90
+ if (!response.ok) {
91
+ throw new Error(`Gap recovery failed: ${response.status} ${response.statusText}`);
92
+ }
93
+ const data = (await response.json());
94
+ if (data.success && data.data) {
95
+ const messages = data.data;
96
+ messages.sort((a, b) => a.seq - b.seq);
97
+ this.setStatus("synced");
98
+ return messages;
99
+ }
100
+ this.setStatus("synced");
101
+ return [];
102
+ }
103
+ catch (error) {
104
+ console.error("Error recovering gap:", error);
105
+ await this.fullResync();
106
+ return [];
107
+ }
108
+ }
109
+ /**
110
+ * Get usable balance for a maker/token pair (or maker/token/tokenId for ERC6909)
111
+ */
112
+ getUsableBalance(maker, token, tokenId) {
113
+ const key = tokenId
114
+ ? `${maker.toLowerCase()}:${token.toLowerCase()}:${tokenId.toString()}`
115
+ : `${maker.toLowerCase()}:${token.toLowerCase()}`;
116
+ const balanceData = this.dataMap.get(key);
117
+ return balanceData?.usableBalance;
118
+ }
119
+ /**
120
+ * Get all balances as a map of key -> balance string
121
+ */
122
+ getBalanceMap() {
123
+ const map = new Map();
124
+ for (const [key, balanceData] of this.dataMap.entries()) {
125
+ map.set(key, balanceData.usableBalance);
126
+ }
127
+ return map;
128
+ }
129
+ /**
130
+ * Get all balances as BalanceData array
131
+ */
132
+ getBalances() {
133
+ return Array.from(this.dataMap.values());
134
+ }
135
+ onBalanceUpdate(callback) {
136
+ this.balanceListeners.add(callback);
137
+ return () => this.balanceListeners.delete(callback);
138
+ }
139
+ }
@@ -0,0 +1,47 @@
1
+ import type { SyncStatus } from "../types.js";
2
+ import { RedisWsClient } from "../redis-ws-client.js";
3
+ /**
4
+ * Base abstract class for sync clients
5
+ * @template TMessage - The message type (e.g., SequencedMessage, BalanceUpdateMessage)
6
+ * @template TChange - The change type (e.g., OrderChange, BalanceData[])
7
+ * @template TData - The data type stored in the map (e.g., StoredOrder, string)
8
+ * @template TConfig - The config type (must have redisUrl and channel)
9
+ */
10
+ export declare abstract class BaseSyncClient<TMessage extends {
11
+ seq: number;
12
+ }, TChange, TData, TConfig extends {
13
+ redisUrl: string;
14
+ channel: string;
15
+ }> {
16
+ protected wsClient: RedisWsClient | null;
17
+ protected config: TConfig;
18
+ protected status: SyncStatus;
19
+ protected lastSeq: number;
20
+ protected incomingQueue: TMessage[];
21
+ protected isProcessing: boolean;
22
+ protected statusListeners: Set<(status: SyncStatus) => void>;
23
+ protected changeListeners: Set<(change: TChange) => void>;
24
+ protected dataMap: Map<string, TData>;
25
+ protected snapshotListeners: Set<(data: TData[]) => void>;
26
+ constructor(config: TConfig);
27
+ connect(): Promise<void>;
28
+ protected enqueueMessage(message: TMessage): void;
29
+ protected processQueue(): Promise<void>;
30
+ protected fullResync(): Promise<void>;
31
+ protected setStatus(status: SyncStatus): void;
32
+ getStatus(): SyncStatus;
33
+ isSynced(): boolean;
34
+ getLastSequence(): number;
35
+ getBufferedCount(): number;
36
+ onStatus(callback: (status: SyncStatus) => void): () => void;
37
+ onChange(callback: (change: TChange) => void): () => void;
38
+ onSnapshot(callback: (data: TData[]) => void): () => void;
39
+ protected notifySnapshotListeners(data: TData[]): void;
40
+ disconnect(): Promise<void>;
41
+ /**
42
+ * Abstract methods to be implemented by child classes
43
+ */
44
+ protected abstract fetchSnapshot(): Promise<void>;
45
+ protected abstract applyMessage(message: TMessage): Promise<void> | void;
46
+ protected abstract recoverGap(fromSeq: number, toSeq: number): Promise<TMessage[]>;
47
+ }
@@ -0,0 +1,154 @@
1
+ import { RedisWsClient } from "../redis-ws-client.js";
2
+ /**
3
+ * Base abstract class for sync clients
4
+ * @template TMessage - The message type (e.g., SequencedMessage, BalanceUpdateMessage)
5
+ * @template TChange - The change type (e.g., OrderChange, BalanceData[])
6
+ * @template TData - The data type stored in the map (e.g., StoredOrder, string)
7
+ * @template TConfig - The config type (must have redisUrl and channel)
8
+ */
9
+ export class BaseSyncClient {
10
+ constructor(config) {
11
+ this.wsClient = null;
12
+ this.status = "disconnected";
13
+ this.lastSeq = 0;
14
+ this.incomingQueue = [];
15
+ this.isProcessing = false;
16
+ this.statusListeners = new Set();
17
+ this.changeListeners = new Set();
18
+ this.dataMap = new Map();
19
+ this.snapshotListeners = new Set();
20
+ this.config = config;
21
+ }
22
+ async connect() {
23
+ this.setStatus("connecting");
24
+ // Only allow ws:// or wss:// URLs
25
+ const wsUrl = this.config.redisUrl;
26
+ if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://")) {
27
+ throw new Error(`Invalid WebSocket URL: ${wsUrl}. Only ws:// and wss:// URLs are supported.`);
28
+ }
29
+ this.wsClient = new RedisWsClient(wsUrl);
30
+ this.wsClient.subscribe(this.config.channel, (messageStr) => {
31
+ try {
32
+ const message = JSON.parse(messageStr);
33
+ this.enqueueMessage(message);
34
+ }
35
+ catch (error) {
36
+ console.error("Error parsing message:", error);
37
+ }
38
+ });
39
+ this.setStatus("syncing");
40
+ await this.fetchSnapshot();
41
+ this.setStatus("synced");
42
+ }
43
+ enqueueMessage(message) {
44
+ this.incomingQueue.push(message);
45
+ this.processQueue();
46
+ }
47
+ async processQueue() {
48
+ if (this.isProcessing)
49
+ return;
50
+ this.isProcessing = true;
51
+ try {
52
+ while (this.incomingQueue.length > 0) {
53
+ const message = this.incomingQueue.shift();
54
+ if (message.seq <= this.lastSeq) {
55
+ continue;
56
+ }
57
+ const expectedSeq = this.lastSeq + 1;
58
+ if (message.seq === expectedSeq) {
59
+ await this.applyMessage(message);
60
+ this.lastSeq = message.seq;
61
+ }
62
+ else if (message.seq > expectedSeq) {
63
+ const recovered = await this.recoverGap(expectedSeq, message.seq - 1);
64
+ for (const msg of recovered) {
65
+ if (msg.seq === this.lastSeq + 1) {
66
+ await this.applyMessage(msg);
67
+ this.lastSeq = msg.seq;
68
+ }
69
+ }
70
+ if (message.seq === this.lastSeq + 1) {
71
+ await this.applyMessage(message);
72
+ this.lastSeq = message.seq;
73
+ }
74
+ else {
75
+ this.incomingQueue.unshift(message);
76
+ await this.recoverGap(this.lastSeq + 1, message.seq - 1);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ finally {
82
+ this.isProcessing = false;
83
+ if (this.incomingQueue.length > 0) {
84
+ setImmediate(() => this.processQueue());
85
+ }
86
+ }
87
+ }
88
+ async fullResync() {
89
+ this.isProcessing = true;
90
+ try {
91
+ this.incomingQueue = [];
92
+ this.lastSeq = 0;
93
+ await this.fetchSnapshot();
94
+ this.setStatus("synced");
95
+ }
96
+ finally {
97
+ this.isProcessing = false;
98
+ }
99
+ }
100
+ setStatus(status) {
101
+ if (this.status === status)
102
+ return;
103
+ this.status = status;
104
+ this.statusListeners.forEach((listener) => {
105
+ try {
106
+ listener(status);
107
+ }
108
+ catch (error) {
109
+ console.error("Error in status listener:", error);
110
+ }
111
+ });
112
+ }
113
+ getStatus() {
114
+ return this.status;
115
+ }
116
+ isSynced() {
117
+ return this.status === "synced";
118
+ }
119
+ getLastSequence() {
120
+ return this.lastSeq;
121
+ }
122
+ getBufferedCount() {
123
+ return this.incomingQueue.length;
124
+ }
125
+ onStatus(callback) {
126
+ this.statusListeners.add(callback);
127
+ return () => this.statusListeners.delete(callback);
128
+ }
129
+ onChange(callback) {
130
+ this.changeListeners.add(callback);
131
+ return () => this.changeListeners.delete(callback);
132
+ }
133
+ onSnapshot(callback) {
134
+ this.snapshotListeners.add(callback);
135
+ return () => this.snapshotListeners.delete(callback);
136
+ }
137
+ notifySnapshotListeners(data) {
138
+ this.snapshotListeners.forEach((listener) => {
139
+ try {
140
+ listener(data);
141
+ }
142
+ catch (error) {
143
+ console.error("Error in snapshot listener:", error);
144
+ }
145
+ });
146
+ }
147
+ async disconnect() {
148
+ if (this.wsClient) {
149
+ this.wsClient.close();
150
+ this.wsClient = null;
151
+ }
152
+ this.setStatus("disconnected");
153
+ }
154
+ }
@@ -0,0 +1,18 @@
1
+ import { type StoredOrder } from "../../shared/types.js";
2
+ import type { OrderChange, SequencedMessage, SyncClientConfig } from "../types.js";
3
+ import { BaseSyncClient } from "./base-client.js";
4
+ export interface OrderbookSyncClientConfig extends SyncClientConfig {
5
+ channel: string;
6
+ }
7
+ export declare class OrderbookSyncClient extends BaseSyncClient<SequencedMessage, OrderChange, StoredOrder, OrderbookSyncClientConfig> {
8
+ private expireCheckInterval;
9
+ constructor(config: SyncClientConfig);
10
+ connect(): Promise<void>;
11
+ protected fetchSnapshot(): Promise<void>;
12
+ protected applyMessage(message: SequencedMessage): void;
13
+ protected recoverGap(fromSeq: number, toSeq: number): Promise<SequencedMessage[]>;
14
+ getOrders(): StoredOrder[];
15
+ getOrder(orderHash: string): StoredOrder | undefined;
16
+ private startExpireCheck;
17
+ disconnect(): Promise<void>;
18
+ }