@velocity-exchange/sdk 0.4.0 → 0.6.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 (323) hide show
  1. package/README.md +3 -3
  2. package/lib/browser/adminClient.d.ts +35 -0
  3. package/lib/browser/adminClient.js +65 -0
  4. package/lib/browser/config.js +6 -7
  5. package/lib/browser/constants/numericConstants.d.ts +1 -1
  6. package/lib/browser/constants/numericConstants.js +1 -1
  7. package/lib/browser/decode/user.js +5 -1
  8. package/lib/browser/idl/velocity.d.ts +146 -3
  9. package/lib/browser/idl/velocity.json +147 -4
  10. package/lib/browser/orderSubscriber/OrderSubscriber.js +3 -0
  11. package/lib/browser/pyth/pythLazerSubscriber.js +37 -27
  12. package/lib/browser/types.d.ts +4 -0
  13. package/lib/browser/user.d.ts +15 -0
  14. package/lib/browser/user.js +37 -1
  15. package/lib/browser/velocityClient.d.ts +46 -10
  16. package/lib/browser/velocityClient.js +107 -35
  17. package/lib/node/adminClient.d.ts +35 -0
  18. package/lib/node/adminClient.d.ts.map +1 -1
  19. package/lib/node/adminClient.js +65 -0
  20. package/lib/node/config.d.ts.map +1 -1
  21. package/lib/node/config.js +6 -7
  22. package/lib/node/constants/numericConstants.d.ts +1 -1
  23. package/lib/node/constants/numericConstants.d.ts.map +1 -1
  24. package/lib/node/constants/numericConstants.js +1 -1
  25. package/lib/node/decode/user.d.ts.map +1 -1
  26. package/lib/node/decode/user.js +5 -1
  27. package/lib/node/idl/velocity.d.ts +146 -3
  28. package/lib/node/idl/velocity.d.ts.map +1 -1
  29. package/lib/node/idl/velocity.json +147 -4
  30. package/lib/node/orderSubscriber/OrderSubscriber.d.ts.map +1 -1
  31. package/lib/node/orderSubscriber/OrderSubscriber.js +3 -0
  32. package/lib/node/pyth/pythLazerSubscriber.d.ts.map +1 -1
  33. package/lib/node/pyth/pythLazerSubscriber.js +37 -27
  34. package/lib/node/types.d.ts +4 -0
  35. package/lib/node/types.d.ts.map +1 -1
  36. package/lib/node/user.d.ts +15 -0
  37. package/lib/node/user.d.ts.map +1 -1
  38. package/lib/node/user.js +37 -1
  39. package/lib/node/velocityClient.d.ts +46 -10
  40. package/lib/node/velocityClient.d.ts.map +1 -1
  41. package/lib/node/velocityClient.js +107 -35
  42. package/package.json +7 -3
  43. package/.prettierignore +0 -1
  44. package/CHANGELOG.md +0 -132
  45. package/build-browser.js +0 -58
  46. package/bunfig.toml +0 -3
  47. package/esbuild-shims.js +0 -12
  48. package/scripts/deposit-isolated-positions.ts +0 -110
  49. package/scripts/find-flagged-users.ts +0 -213
  50. package/scripts/grpc-client-test-comparison.ts +0 -372
  51. package/scripts/grpc-multiuser-client-test-comparison.ts +0 -158
  52. package/scripts/postbuild.js +0 -95
  53. package/scripts/single-grpc-client-test.ts +0 -284
  54. package/scripts/withdraw-isolated-positions.ts +0 -174
  55. package/src/accounts/README_WebSocketAccountSubscriberV2.md +0 -95
  56. package/src/accounts/README_WebSocketProgramAccountSubscriberV2.md +0 -135
  57. package/src/accounts/basicUserAccountSubscriber.ts +0 -79
  58. package/src/accounts/basicUserStatsAccountSubscriber.ts +0 -87
  59. package/src/accounts/bulkAccountLoader.ts +0 -358
  60. package/src/accounts/bulkUserStatsSubscription.ts +0 -40
  61. package/src/accounts/bulkUserSubscription.ts +0 -39
  62. package/src/accounts/customizedCadenceBulkAccountLoader.ts +0 -250
  63. package/src/accounts/fetch.ts +0 -200
  64. package/src/accounts/grpcAccountSubscriber.ts +0 -225
  65. package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -73
  66. package/src/accounts/grpcMultiAccountSubscriber.ts +0 -566
  67. package/src/accounts/grpcMultiUserAccountSubscriber.ts +0 -328
  68. package/src/accounts/grpcProgramAccountSubscriber.ts +0 -264
  69. package/src/accounts/grpcUserAccountSubscriber.ts +0 -63
  70. package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -65
  71. package/src/accounts/grpcVelocityClientAccountSubscriber.ts +0 -257
  72. package/src/accounts/grpcVelocityClientAccountSubscriberV2.ts +0 -941
  73. package/src/accounts/laserProgramAccountSubscriber.ts +0 -266
  74. package/src/accounts/oneShotUserAccountSubscriber.ts +0 -88
  75. package/src/accounts/oneShotUserStatsAccountSubscriber.ts +0 -94
  76. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -223
  77. package/src/accounts/pollingOracleAccountSubscriber.ts +0 -151
  78. package/src/accounts/pollingTokenAccountSubscriber.ts +0 -147
  79. package/src/accounts/pollingUserAccountSubscriber.ts +0 -198
  80. package/src/accounts/pollingUserStatsAccountSubscriber.ts +0 -180
  81. package/src/accounts/pollingVelocityClientAccountSubscriber.ts +0 -774
  82. package/src/accounts/testBulkAccountLoader.ts +0 -59
  83. package/src/accounts/types.ts +0 -428
  84. package/src/accounts/utils.ts +0 -90
  85. package/src/accounts/webSocketAccountSubscriber.ts +0 -304
  86. package/src/accounts/webSocketAccountSubscriberV2.ts +0 -536
  87. package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +0 -171
  88. package/src/accounts/webSocketProgramAccountSubscriber.ts +0 -244
  89. package/src/accounts/webSocketProgramAccountSubscriberV2.ts +0 -1048
  90. package/src/accounts/webSocketProgramAccountsSubscriberV2.ts +0 -1042
  91. package/src/accounts/webSocketUserAccountSubscriber.ts +0 -139
  92. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +0 -131
  93. package/src/accounts/webSocketVelocityClientAccountSubscriber.ts +0 -865
  94. package/src/accounts/webSocketVelocityClientAccountSubscriberV2.ts +0 -897
  95. package/src/accounts/websocketProgramUserAccountSubscriber.ts +0 -127
  96. package/src/addresses/marketAddresses.ts +0 -28
  97. package/src/addresses/pda.ts +0 -620
  98. package/src/adminClient.ts +0 -8136
  99. package/src/assert/assert.ts +0 -5
  100. package/src/auctionSubscriber/auctionSubscriber.ts +0 -86
  101. package/src/auctionSubscriber/auctionSubscriberGrpc.ts +0 -99
  102. package/src/auctionSubscriber/index.ts +0 -9
  103. package/src/auctionSubscriber/types.ts +0 -30
  104. package/src/bankrun/bankrunConnection.ts +0 -630
  105. package/src/blockhashSubscriber/BlockhashSubscriber.ts +0 -160
  106. package/src/blockhashSubscriber/index.ts +0 -6
  107. package/src/blockhashSubscriber/types.ts +0 -13
  108. package/src/clock/clockSubscriber.ts +0 -146
  109. package/src/config.ts +0 -312
  110. package/src/constants/index.ts +0 -9
  111. package/src/constants/numericConstants.ts +0 -194
  112. package/src/constants/perpMarkets.ts +0 -127
  113. package/src/constants/spotMarkets.ts +0 -104
  114. package/src/constants/txConstants.ts +0 -2
  115. package/src/constituentMap/constituentMap.ts +0 -305
  116. package/src/constituentMap/pollingConstituentAccountSubscriber.ts +0 -99
  117. package/src/constituentMap/webSocketConstituentAccountSubscriber.ts +0 -121
  118. package/src/core/VelocityCore.ts +0 -606
  119. package/src/core/index.ts +0 -12
  120. package/src/core/instructions/deposit.ts +0 -64
  121. package/src/core/instructions/fill.ts +0 -51
  122. package/src/core/instructions/funding.ts +0 -33
  123. package/src/core/instructions/liquidation.ts +0 -55
  124. package/src/core/instructions/orders.ts +0 -84
  125. package/src/core/instructions/perpOrders.ts +0 -312
  126. package/src/core/instructions/settlement.ts +0 -40
  127. package/src/core/instructions/trigger.ts +0 -41
  128. package/src/core/instructions/withdraw.ts +0 -65
  129. package/src/core/remainingAccounts.ts +0 -320
  130. package/src/core/signedMsg.ts +0 -109
  131. package/src/decode/customCoder.ts +0 -148
  132. package/src/decode/user.ts +0 -401
  133. package/src/dlob/DLOB.ts +0 -2793
  134. package/src/dlob/DLOBNode.ts +0 -305
  135. package/src/dlob/DLOBSubscriber.ts +0 -269
  136. package/src/dlob/NodeList.ts +0 -244
  137. package/src/dlob/orderBookLevels.ts +0 -695
  138. package/src/dlob/types.ts +0 -37
  139. package/src/events/eventList.ts +0 -114
  140. package/src/events/eventSubscriber.ts +0 -521
  141. package/src/events/eventsServerLogProvider.ts +0 -189
  142. package/src/events/fetchLogs.ts +0 -219
  143. package/src/events/parse.ts +0 -271
  144. package/src/events/pollingLogProvider.ts +0 -113
  145. package/src/events/sort.ts +0 -51
  146. package/src/events/txEventCache.ts +0 -92
  147. package/src/events/types.ts +0 -337
  148. package/src/events/webSocketLogProvider.ts +0 -152
  149. package/src/factory/bigNum.ts +0 -709
  150. package/src/factory/oracleClient.ts +0 -73
  151. package/src/idl/pyth.json +0 -142
  152. package/src/idl/pyth.ts +0 -97
  153. package/src/idl/pyth_solana_receiver.json +0 -628
  154. package/src/idl/token_faucet.json +0 -229
  155. package/src/idl/token_faucet.ts +0 -197
  156. package/src/idl/velocity.json +0 -23974
  157. package/src/idl/velocity.ts +0 -23980
  158. package/src/index.ts +0 -150
  159. package/src/indicative-quotes/indicativeQuotesSender.ts +0 -233
  160. package/src/isomorphic/README.md +0 -19
  161. package/src/isomorphic/anchor.browser.ts +0 -44
  162. package/src/isomorphic/anchor.node.ts +0 -1
  163. package/src/isomorphic/anchor.ts +0 -1
  164. package/src/isomorphic/anchor29.browser.ts +0 -24
  165. package/src/isomorphic/anchor29.node.ts +0 -1
  166. package/src/isomorphic/anchor29.ts +0 -1
  167. package/src/isomorphic/grpc.browser.ts +0 -4
  168. package/src/isomorphic/grpc.node.ts +0 -130
  169. package/src/isomorphic/grpc.ts +0 -1
  170. package/src/jupiter/jupiterClient.ts +0 -502
  171. package/src/keypair.ts +0 -32
  172. package/src/margin/README.md +0 -138
  173. package/src/marginCalculation.ts +0 -407
  174. package/src/marinade/idl/idl.json +0 -1962
  175. package/src/marinade/index.ts +0 -73
  176. package/src/marinade/types.ts +0 -3925
  177. package/src/math/amm.ts +0 -1758
  178. package/src/math/auction.ts +0 -480
  179. package/src/math/bankruptcy.ts +0 -138
  180. package/src/math/builder.ts +0 -130
  181. package/src/math/conversion.ts +0 -45
  182. package/src/math/exchangeStatus.ts +0 -261
  183. package/src/math/funding.ts +0 -498
  184. package/src/math/insurance.ts +0 -171
  185. package/src/math/liquidation.ts +0 -433
  186. package/src/math/margin.ts +0 -327
  187. package/src/math/market.ts +0 -487
  188. package/src/math/oracles.ts +0 -477
  189. package/src/math/orders.ts +0 -500
  190. package/src/math/position.ts +0 -381
  191. package/src/math/repeg.ts +0 -223
  192. package/src/math/spotBalance.ts +0 -996
  193. package/src/math/spotMarket.ts +0 -115
  194. package/src/math/spotPosition.ts +0 -263
  195. package/src/math/state.ts +0 -71
  196. package/src/math/superStake.ts +0 -602
  197. package/src/math/tiers.ts +0 -73
  198. package/src/math/trade.ts +0 -879
  199. package/src/math/utils.ts +0 -183
  200. package/src/memcmp.ts +0 -279
  201. package/src/oracles/oracleClientCache.ts +0 -41
  202. package/src/oracles/oracleId.ts +0 -99
  203. package/src/oracles/prelaunchOracleClient.ts +0 -63
  204. package/src/oracles/pythClient.ts +0 -125
  205. package/src/oracles/pythLazerClient.ts +0 -141
  206. package/src/oracles/quoteAssetOracleClient.ts +0 -43
  207. package/src/oracles/strictOraclePrice.ts +0 -40
  208. package/src/oracles/types.ts +0 -82
  209. package/src/oracles/utils.ts +0 -45
  210. package/src/orderParams.ts +0 -117
  211. package/src/orderSubscriber/OrderSubscriber.ts +0 -378
  212. package/src/orderSubscriber/PollingSubscription.ts +0 -50
  213. package/src/orderSubscriber/WebsocketSubscription.ts +0 -137
  214. package/src/orderSubscriber/grpcSubscription.ts +0 -155
  215. package/src/orderSubscriber/index.ts +0 -7
  216. package/src/orderSubscriber/types.ts +0 -64
  217. package/src/priorityFee/averageOverSlotsStrategy.ts +0 -17
  218. package/src/priorityFee/averageStrategy.ts +0 -13
  219. package/src/priorityFee/ewmaStrategy.ts +0 -51
  220. package/src/priorityFee/heliusPriorityFeeMethod.ts +0 -68
  221. package/src/priorityFee/index.ts +0 -16
  222. package/src/priorityFee/maxOverSlotsStrategy.ts +0 -18
  223. package/src/priorityFee/maxStrategy.ts +0 -8
  224. package/src/priorityFee/priorityFeeSubscriber.ts +0 -328
  225. package/src/priorityFee/priorityFeeSubscriberMap.ts +0 -134
  226. package/src/priorityFee/solanaPriorityFeeMethod.ts +0 -46
  227. package/src/priorityFee/types.ts +0 -82
  228. package/src/priorityFee/velocityPriorityFeeMethod.ts +0 -53
  229. package/src/pyth/constants.ts +0 -9
  230. package/src/pyth/index.ts +0 -15
  231. package/src/pyth/pythLazerSubscriber.ts +0 -365
  232. package/src/pyth/types.ts +0 -4451
  233. package/src/pyth/utils.ts +0 -13
  234. package/src/slot/SlotSubscriber.ts +0 -126
  235. package/src/slot/SlothashSubscriber.ts +0 -163
  236. package/src/swap/UnifiedSwapClient.ts +0 -315
  237. package/src/swift/grpcSignedMsgUserAccountSubscriber.ts +0 -81
  238. package/src/swift/index.ts +0 -10
  239. package/src/swift/signedMsgUserAccountSubscriber.ts +0 -251
  240. package/src/swift/swiftOrderSubscriber.ts +0 -351
  241. package/src/testClient.ts +0 -42
  242. package/src/titan/titanClient.ts +0 -438
  243. package/src/token/index.ts +0 -13
  244. package/src/tokenFaucet.ts +0 -296
  245. package/src/tx/baseTxSender.ts +0 -651
  246. package/src/tx/blockhashFetcher/baseBlockhashFetcher.ts +0 -31
  247. package/src/tx/blockhashFetcher/cachedBlockhashFetcher.ts +0 -105
  248. package/src/tx/blockhashFetcher/types.ts +0 -9
  249. package/src/tx/fastSingleTxSender.ts +0 -200
  250. package/src/tx/forwardOnlyTxSender.ts +0 -193
  251. package/src/tx/priorityFeeCalculator.ts +0 -117
  252. package/src/tx/reportTransactionError.ts +0 -176
  253. package/src/tx/retryTxSender.ts +0 -177
  254. package/src/tx/txHandler.ts +0 -1009
  255. package/src/tx/txParamProcessor.ts +0 -225
  256. package/src/tx/types.ts +0 -117
  257. package/src/tx/utils.ts +0 -133
  258. package/src/tx/whileValidTxSender.ts +0 -377
  259. package/src/types.ts +0 -2575
  260. package/src/user.ts +0 -5316
  261. package/src/userConfig.ts +0 -38
  262. package/src/userMap/PollingSubscription.ts +0 -61
  263. package/src/userMap/WebsocketSubscription.ts +0 -93
  264. package/src/userMap/grpcSubscription.ts +0 -93
  265. package/src/userMap/referrerMap.ts +0 -360
  266. package/src/userMap/revenueShareEscrowMap.ts +0 -342
  267. package/src/userMap/userMap.ts +0 -784
  268. package/src/userMap/userMapConfig.ts +0 -82
  269. package/src/userMap/userStatsMap.ts +0 -393
  270. package/src/userName.ts +0 -37
  271. package/src/userStats.ts +0 -159
  272. package/src/userStatsConfig.ts +0 -35
  273. package/src/util/TransactionConfirmationManager.ts +0 -333
  274. package/src/util/chainClock.ts +0 -65
  275. package/src/util/computeUnits.ts +0 -101
  276. package/src/util/digest.ts +0 -33
  277. package/src/util/ed25519Utils.ts +0 -95
  278. package/src/util/promiseTimeout.ts +0 -23
  279. package/src/velocityClient.ts +0 -14802
  280. package/src/velocityClientConfig.ts +0 -119
  281. package/src/wallet.ts +0 -91
  282. package/tests/VelocityCore/builder_escrow.test.ts +0 -121
  283. package/tests/VelocityCore/decode.test.ts +0 -16
  284. package/tests/VelocityCore/fill_trigger.test.ts +0 -73
  285. package/tests/VelocityCore/instructions.test.ts +0 -46
  286. package/tests/VelocityCore/pdas.test.ts +0 -30
  287. package/tests/VelocityCore/perp_orders.test.ts +0 -205
  288. package/tests/VelocityCore/remainingAccounts.test.ts +0 -72
  289. package/tests/VelocityCore/settlement_liquidation.test.ts +0 -69
  290. package/tests/accounts/customizedCadenceBulkAccountLoader.test.ts +0 -211
  291. package/tests/amm/test.ts +0 -2402
  292. package/tests/auctions/test.ts +0 -81
  293. package/tests/bn/test.ts +0 -355
  294. package/tests/builder/builderFee.test.ts +0 -42
  295. package/tests/ci/verifyConstants.ts +0 -393
  296. package/tests/decode/test.ts +0 -262
  297. package/tests/decode/userAccountBufferStrings.ts +0 -102
  298. package/tests/dlob/helpers.ts +0 -748
  299. package/tests/dlob/test.ts +0 -6969
  300. package/tests/dlob/tickSizeStandardization.ts +0 -545
  301. package/tests/events/parseLogsForCuUsage.ts +0 -139
  302. package/tests/exchangeStatus/test.ts +0 -45
  303. package/tests/insurance/test.ts +0 -40
  304. package/tests/liquidation/test.ts +0 -125
  305. package/tests/oracles/mmOracleGate.test.ts +0 -379
  306. package/tests/oracles/pythPegSnap.test.ts +0 -76
  307. package/tests/sdkParity/enumParity.test.ts +0 -84
  308. package/tests/sdkParity/marginCategoryFill.test.ts +0 -143
  309. package/tests/sdkParity/memcmpOffsets.test.ts +0 -139
  310. package/tests/spot/test.ts +0 -281
  311. package/tests/tx/TransactionConfirmationManager.test.ts +0 -312
  312. package/tests/tx/cachedBlockhashFetcher.test.ts +0 -96
  313. package/tests/tx/priorityFeeCalculator.ts +0 -77
  314. package/tests/tx/priorityFeeStrategy.ts +0 -95
  315. package/tests/user/bankruptcy.ts +0 -165
  316. package/tests/user/feeAndWithdrawLimits.ts +0 -284
  317. package/tests/user/getMarginCalculation.ts +0 -586
  318. package/tests/user/helpers.ts +0 -184
  319. package/tests/user/liquidations.ts +0 -129
  320. package/tests/user/marginCalculations.test.ts +0 -321
  321. package/tests/user/test.ts +0 -833
  322. package/tsconfig.browser.json +0 -18
  323. package/tsconfig.json +0 -36
@@ -1,941 +0,0 @@
1
- import StrictEventEmitter from 'strict-event-emitter-types';
2
- import { EventEmitter } from 'events';
3
- import { OracleInfo, OraclePriceData } from '../oracles/types';
4
- import { AccountInfo, PublicKey } from '@solana/web3.js';
5
- import { findAllMarketAndOracles, VelocityProgram } from '../config';
6
- import {
7
- getVelocityStateAccountPublicKey,
8
- getPerpMarketPublicKey,
9
- getPerpMarketPublicKeySync,
10
- getSpotMarketPublicKey,
11
- getSpotMarketPublicKeySync,
12
- } from '../addresses/pda';
13
- import {
14
- AccountSubscriber,
15
- DataAndSlot,
16
- DelistedMarketSetting,
17
- VelocityClientAccountEvents,
18
- VelocityClientAccountSubscriber,
19
- NotSubscribedError,
20
- GrpcConfigs,
21
- ResubOpts,
22
- } from './types';
23
- import { grpcAccountSubscriber } from './grpcAccountSubscriber';
24
- import { grpcMultiAccountSubscriber } from './grpcMultiAccountSubscriber';
25
- import { PerpMarketAccount, SpotMarketAccount, StateAccount } from '../types';
26
- import {
27
- getOracleId,
28
- getPublicKeyAndSourceFromOracleId,
29
- } from '../oracles/oracleId';
30
- import { OracleClientCache } from '../oracles/oracleClientCache';
31
- import { findDelistedPerpMarketsAndOracles } from './utils';
32
-
33
- /**
34
- * `VelocityClientAccountSubscriber` variant that, unlike `grpcVelocityClientAccountSubscriber`
35
- * (one gRPC stream per account), multiplexes all perp markets onto one `grpcMultiAccountSubscriber`,
36
- * all spot markets onto a second, and all oracles onto a third — three gRPC streams total instead
37
- * of one per account. Lower connection/stream overhead at scale; `getOraclePriceDataAndSlot`
38
- * relies on this class's own `oracleIdToOracleDataMap` rather than the multi-subscriber's account
39
- * map, since a single oracle pubkey can back multiple `(pubkey, source)` oracle ids (e.g. a market
40
- * pair sharing an underlying price feed).
41
- */
42
- export class grpcVelocityClientAccountSubscriberV2
43
- implements VelocityClientAccountSubscriber
44
- {
45
- private grpcConfigs: GrpcConfigs;
46
- private perpMarketsSubscriber?: grpcMultiAccountSubscriber<PerpMarketAccount>;
47
- private spotMarketsSubscriber?: grpcMultiAccountSubscriber<SpotMarketAccount>;
48
- private oracleMultiSubscriber?: grpcMultiAccountSubscriber<
49
- OraclePriceData,
50
- OracleInfo
51
- >;
52
- private perpMarketIndexToAccountPubkeyMap = new Map<number, string>();
53
- private spotMarketIndexToAccountPubkeyMap = new Map<number, string>();
54
- private delistedMarketSetting: DelistedMarketSetting;
55
-
56
- public eventEmitter: StrictEventEmitter<
57
- EventEmitter,
58
- VelocityClientAccountEvents
59
- >;
60
- public isSubscribed: boolean;
61
- public isSubscribing: boolean;
62
- public program: VelocityProgram;
63
- public perpMarketIndexes: number[];
64
- public spotMarketIndexes: number[];
65
- public shouldFindAllMarketsAndOracles: boolean;
66
- public oracleInfos: OracleInfo[];
67
- public initialPerpMarketAccountData: Map<number, PerpMarketAccount>;
68
- public initialSpotMarketAccountData: Map<number, SpotMarketAccount>;
69
- public initialOraclePriceData: Map<string, OraclePriceData>;
70
- public perpOracleMap = new Map<number, PublicKey>();
71
- public perpOracleStringMap = new Map<number, string>();
72
- public spotOracleMap = new Map<number, PublicKey>();
73
- public spotOracleStringMap = new Map<number, string>();
74
- private oracleIdToOracleDataMap = new Map<
75
- string,
76
- DataAndSlot<OraclePriceData>
77
- >();
78
- public stateAccountSubscriber?: AccountSubscriber<StateAccount>;
79
- oracleClientCache = new OracleClientCache();
80
- private resubOpts?: ResubOpts;
81
-
82
- private subscriptionPromiseResolver: (val: boolean) => void = () => {};
83
- private subscriptionPromise: Promise<boolean> = Promise.resolve(false);
84
-
85
- /**
86
- * @param grpcConfigs gRPC Geyser endpoint/token/commitment config (Yellowstone or LaserStream).
87
- * @param program Anchor program used to derive PDAs, decode accounts, and resolve oracle clients.
88
- * @param perpMarketIndexes Perp market indexes to track, if `shouldFindAllMarketsAndOracles` is false.
89
- * @param spotMarketIndexes Spot market indexes to track, if `shouldFindAllMarketsAndOracles` is false.
90
- * @param oracleInfos Oracles to track up front, if `shouldFindAllMarketsAndOracles` is false.
91
- * @param shouldFindAllMarketsAndOracles If true, `subscribe()` first discovers every market/oracle from on-chain state.
92
- * @param delistedMarketSetting Behavior applied to delisted perp markets/oracles after subscribing.
93
- * @param resubOpts Resubscription watchdog options passed to the underlying multi-account subscribers.
94
- */
95
- constructor(
96
- grpcConfigs: GrpcConfigs,
97
- program: VelocityProgram,
98
- perpMarketIndexes: number[],
99
- spotMarketIndexes: number[],
100
- oracleInfos: OracleInfo[],
101
- shouldFindAllMarketsAndOracles: boolean,
102
- delistedMarketSetting: DelistedMarketSetting,
103
- resubOpts?: ResubOpts
104
- ) {
105
- this.eventEmitter = new EventEmitter();
106
- this.isSubscribed = false;
107
- this.isSubscribing = false;
108
- this.program = program;
109
- this.perpMarketIndexes = perpMarketIndexes;
110
- this.spotMarketIndexes = spotMarketIndexes;
111
- this.shouldFindAllMarketsAndOracles = shouldFindAllMarketsAndOracles;
112
- this.oracleInfos = oracleInfos;
113
- this.initialPerpMarketAccountData = new Map();
114
- this.initialSpotMarketAccountData = new Map();
115
- this.initialOraclePriceData = new Map();
116
- this.perpOracleMap = new Map();
117
- this.perpOracleStringMap = new Map();
118
- this.spotOracleMap = new Map();
119
- this.spotOracleStringMap = new Map();
120
- this.grpcConfigs = grpcConfigs;
121
- this.resubOpts = resubOpts;
122
- this.delistedMarketSetting = delistedMarketSetting;
123
- }
124
-
125
- chunks = <T>(array: readonly T[], size: number): T[][] => {
126
- return new Array(Math.ceil(array.length / size))
127
- .fill(null)
128
- .map((_, index) => index * size)
129
- .map((begin) => array.slice(begin, begin + size));
130
- };
131
-
132
- /**
133
- * Batch-fetches every tracked perp market, spot market, and oracle account via chunked
134
- * `getMultipleAccountsInfo` calls (75 pubkeys per chunk) and stashes the decoded results in
135
- * `initialPerpMarketAccountData`/`initialSpotMarketAccountData`/`initialOraclePriceData`, which
136
- * the multi-account subscribers seed from before their gRPC streams deliver live data. Skips
137
- * data already populated (e.g. by `findAllMarketAndOracles` when `shouldFindAllMarketsAndOracles`
138
- * is set).
139
- */
140
- async setInitialData(): Promise<void> {
141
- const connection = this.program.provider.connection;
142
-
143
- if (
144
- !this.initialPerpMarketAccountData ||
145
- this.initialPerpMarketAccountData.size === 0
146
- ) {
147
- const perpMarketPublicKeys = this.perpMarketIndexes.map((marketIndex) =>
148
- getPerpMarketPublicKeySync(this.program.programId, marketIndex)
149
- );
150
- const perpMarketPublicKeysChunks = this.chunks(perpMarketPublicKeys, 75);
151
- const perpMarketAccountInfos = (
152
- await Promise.all(
153
- perpMarketPublicKeysChunks.map((perpMarketPublicKeysChunk) =>
154
- connection.getMultipleAccountsInfo(perpMarketPublicKeysChunk)
155
- )
156
- )
157
- ).flat();
158
- this.initialPerpMarketAccountData = new Map(
159
- perpMarketAccountInfos
160
- .filter(
161
- (accountInfo): accountInfo is AccountInfo<Buffer> => !!accountInfo
162
- )
163
- .map((accountInfo) => {
164
- const perpMarket = this.program.coder.accounts.decode(
165
- 'perpMarket',
166
- accountInfo.data
167
- );
168
- return [perpMarket.marketIndex, perpMarket];
169
- })
170
- );
171
- }
172
-
173
- if (
174
- !this.initialSpotMarketAccountData ||
175
- this.initialSpotMarketAccountData.size === 0
176
- ) {
177
- const spotMarketPublicKeys = this.spotMarketIndexes.map((marketIndex) =>
178
- getSpotMarketPublicKeySync(this.program.programId, marketIndex)
179
- );
180
- const spotMarketPublicKeysChunks = this.chunks(spotMarketPublicKeys, 75);
181
- const spotMarketAccountInfos = (
182
- await Promise.all(
183
- spotMarketPublicKeysChunks.map((spotMarketPublicKeysChunk) =>
184
- connection.getMultipleAccountsInfo(spotMarketPublicKeysChunk)
185
- )
186
- )
187
- ).flat();
188
- this.initialSpotMarketAccountData = new Map(
189
- spotMarketAccountInfos
190
- .filter(
191
- (accountInfo): accountInfo is AccountInfo<Buffer> => !!accountInfo
192
- )
193
- .map((accountInfo) => {
194
- const spotMarket = this.program.coder.accounts.decode(
195
- 'spotMarket',
196
- accountInfo.data
197
- );
198
- return [spotMarket.marketIndex, spotMarket];
199
- })
200
- );
201
- }
202
-
203
- const oracleAccountPubkeyChunks = this.chunks(
204
- this.oracleInfos.map((oracleInfo) => oracleInfo.publicKey),
205
- 75
206
- );
207
- const oracleAccountInfos = (
208
- await Promise.all(
209
- oracleAccountPubkeyChunks.map((oracleAccountPublicKeysChunk) =>
210
- connection.getMultipleAccountsInfo(oracleAccountPublicKeysChunk)
211
- )
212
- )
213
- ).flat();
214
- this.initialOraclePriceData = new Map(
215
- this.oracleInfos.reduce<[string, OraclePriceData][]>(
216
- (result, oracleInfo, i) => {
217
- const oracleAccountInfo = oracleAccountInfos[i];
218
- if (!oracleAccountInfo) {
219
- return result;
220
- }
221
- const oracleClient = this.oracleClientCache.get(
222
- oracleInfo.source,
223
- connection,
224
- this.program
225
- );
226
- if (!oracleClient) {
227
- return result;
228
- }
229
- const oraclePriceData = oracleClient.getOraclePriceDataFromBuffer(
230
- oracleAccountInfo.data
231
- );
232
- result.push([
233
- getOracleId(oracleInfo.publicKey, oracleInfo.source),
234
- oraclePriceData,
235
- ]);
236
- return result;
237
- },
238
- []
239
- )
240
- );
241
- }
242
-
243
- /**
244
- * Records `_marketIndex` in `perpMarketIndexes` for bookkeeping. Note this does **not**
245
- * actually add the account to the live `perpMarketsSubscriber` gRPC stream — unlike the
246
- * WebSocket/per-account subscribers' `addPerpMarket`, no new subscription is created here.
247
- * @param _marketIndex Perp market index to record.
248
- */
249
- async addPerpMarket(_marketIndex: number): Promise<boolean> {
250
- if (!this.perpMarketIndexes.includes(_marketIndex)) {
251
- this.perpMarketIndexes = this.perpMarketIndexes.concat(_marketIndex);
252
- }
253
- return true;
254
- }
255
-
256
- /** No-op; unlike `addPerpMarket`, does not even record the index. Always resolves `true`. */
257
- async addSpotMarket(_marketIndex: number): Promise<boolean> {
258
- return true;
259
- }
260
-
261
- /**
262
- * Adds an oracle to `oracleInfos` and, if the oracle multi-subscriber's gRPC stream is already
263
- * active, calls `oracleMultiSubscriber.addAccounts` to extend it. A no-op that resolves `true`
264
- * immediately for the `PublicKey.default` sentinel (quote-asset "no oracle") or an oracle
265
- * already tracked.
266
- * @param oracleInfo Oracle pubkey and source to start tracking.
267
- */
268
- async addOracle(oracleInfo: OracleInfo): Promise<boolean> {
269
- if (this.resubOpts?.logResubMessages) {
270
- console.log('[grpcVelocityClientAccountSubscriberV2] addOracle');
271
- }
272
- if (oracleInfo.publicKey.equals(PublicKey.default)) {
273
- return true;
274
- }
275
-
276
- const exists = this.oracleInfos.some(
277
- (o) =>
278
- o.source === oracleInfo.source &&
279
- o.publicKey.equals(oracleInfo.publicKey)
280
- );
281
- if (exists) {
282
- return true; // Already exists, don't add duplicate
283
- }
284
-
285
- this.oracleInfos = this.oracleInfos.concat(oracleInfo);
286
-
287
- // extend the multi-subscriber's accountPropsMap alongside the pubkey filter, so the
288
- // oracle decode path has the OracleInfo(s) it needs for the newly added feed (all infos
289
- // sharing this pubkey, mirroring the fan-out map built in subscribeToOracles)
290
- const pubkey = oracleInfo.publicKey.toBase58();
291
- const infosForPubkey = this.oracleInfos.filter(
292
- (o) => o.publicKey.toBase58() === pubkey
293
- );
294
- const accountProps = new Map<string, OracleInfo | OracleInfo[]>([
295
- [pubkey, infosForPubkey],
296
- ]);
297
- this.oracleMultiSubscriber?.addAccounts(
298
- [oracleInfo.publicKey],
299
- accountProps
300
- );
301
-
302
- return true;
303
- }
304
-
305
- /**
306
- * Subscribes the `State` account (single-account gRPC subscriber), batch-seeds all market/
307
- * oracle accounts via `setInitialData()`, then opens the three multiplexed
308
- * `grpcMultiAccountSubscriber` streams (perp markets, spot markets, oracles). Applies
309
- * `delistedMarketSetting` afterward. Idempotent: a no-op if already subscribed, and concurrent
310
- * calls while a subscribe is in flight share the same result via `subscriptionPromise`.
311
- */
312
- public async subscribe(): Promise<boolean> {
313
- if (this.isSubscribed) {
314
- return true;
315
- }
316
-
317
- if (this.isSubscribing) {
318
- return await this.subscriptionPromise;
319
- }
320
-
321
- this.isSubscribing = true;
322
-
323
- this.subscriptionPromise = new Promise((res) => {
324
- this.subscriptionPromiseResolver = res;
325
- });
326
-
327
- try {
328
- return await this.subscribeInner();
329
- } catch (err) {
330
- // tear down any partially-created subscribers so a retry starts clean and doesn't
331
- // stack duplicate gRPC streams / leak callbacks — unsubscribe() bails while
332
- // isSubscribed is still false, so it can't recover these on its own
333
- try {
334
- await this.stateAccountSubscriber?.unsubscribe();
335
- await this.oracleMultiSubscriber?.unsubscribe();
336
- await this.perpMarketsSubscriber?.unsubscribe();
337
- await this.spotMarketsSubscriber?.unsubscribe();
338
- } catch (teardownErr) {
339
- console.error(
340
- '[grpcVelocityClientAccountSubscriberV2] cleanup after failed subscribe threw',
341
- teardownErr
342
- );
343
- }
344
- this.stateAccountSubscriber = undefined;
345
- this.oracleMultiSubscriber = undefined;
346
- this.perpMarketsSubscriber = undefined;
347
- this.spotMarketsSubscriber = undefined;
348
-
349
- // settle the shared promise so concurrent subscribe() callers don't hang forever
350
- this.isSubscribing = false;
351
- this.subscriptionPromiseResolver(false);
352
- throw err;
353
- }
354
- }
355
-
356
- private async subscribeInner(): Promise<boolean> {
357
- if (this.shouldFindAllMarketsAndOracles) {
358
- const {
359
- perpMarketIndexes,
360
- perpMarketAccounts,
361
- spotMarketIndexes,
362
- spotMarketAccounts,
363
- oracleInfos,
364
- } = await findAllMarketAndOracles(this.program);
365
- this.perpMarketIndexes = perpMarketIndexes;
366
- this.spotMarketIndexes = spotMarketIndexes;
367
- this.oracleInfos = oracleInfos;
368
- // front run and set the initial data here to save extra gma call in set initial data
369
- this.initialPerpMarketAccountData = new Map(
370
- perpMarketAccounts.map((market) => [market.marketIndex, market])
371
- );
372
- this.initialSpotMarketAccountData = new Map(
373
- spotMarketAccounts.map((market) => [market.marketIndex, market])
374
- );
375
- }
376
-
377
- const statePublicKey = await getVelocityStateAccountPublicKey(
378
- this.program.programId
379
- );
380
-
381
- // create and activate main state account subscription
382
- this.stateAccountSubscriber =
383
- await grpcAccountSubscriber.create<StateAccount>(
384
- this.grpcConfigs,
385
- 'state',
386
- this.program,
387
- statePublicKey,
388
- undefined,
389
- undefined
390
- );
391
- await this.stateAccountSubscriber.subscribe((data: StateAccount) => {
392
- this.eventEmitter.emit('stateAccountUpdate', data);
393
- this.eventEmitter.emit('update');
394
- });
395
-
396
- // set initial data to avoid spamming getAccountInfo calls in webSocketAccountSubscriber
397
- await this.setInitialData();
398
-
399
- // subscribe to perp + spot markets (separate) and oracles
400
- await Promise.all([
401
- this.subscribeToPerpMarketAccounts(),
402
- this.subscribeToSpotMarketAccounts(),
403
- this.subscribeToOracles(),
404
- ]);
405
-
406
- this.eventEmitter.emit('update');
407
-
408
- await this.handleDelistedMarkets();
409
-
410
- await Promise.all([this.setPerpOracleMap(), this.setSpotOracleMap()]);
411
-
412
- this.subscriptionPromiseResolver(true);
413
-
414
- this.isSubscribing = false;
415
- this.isSubscribed = true;
416
-
417
- // delete initial data
418
- this.removeInitialData();
419
-
420
- return true;
421
- }
422
-
423
- /** Fetches the `State` account and all three multiplexed subscribers (perp markets, spot markets, oracles) in sequence. */
424
- public async fetch(): Promise<void> {
425
- await this.stateAccountSubscriber?.fetch();
426
- await this.perpMarketsSubscriber?.fetch();
427
- await this.spotMarketsSubscriber?.fetch();
428
- await this.oracleMultiSubscriber?.fetch();
429
- }
430
-
431
- /** Throws `NotSubscribedError` if `subscribe()` has not been called. */
432
- private assertIsSubscribed(): void {
433
- if (!this.isSubscribed) {
434
- throw new NotSubscribedError(
435
- 'You must call `subscribe` before using this function'
436
- );
437
- }
438
- }
439
-
440
- /** Throws `NotSubscribedError` if not subscribed. */
441
- public getStateAccountAndSlot(): DataAndSlot<StateAccount> {
442
- this.assertIsSubscribed();
443
- return this.stateAccountSubscriber!.dataAndSlot!;
444
- }
445
-
446
- /** Returns every currently cached (loaded) perp market from the multiplexed subscriber. Does not throw `NotSubscribedError`. */
447
- public getMarketAccountsAndSlots(): DataAndSlot<PerpMarketAccount>[] {
448
- const map = this.perpMarketsSubscriber?.getAccountDataMap();
449
- return Array.from(map?.values() ?? []);
450
- }
451
-
452
- /** Returns every currently cached (loaded) spot market from the multiplexed subscriber. Does not throw `NotSubscribedError`. */
453
- public getSpotMarketAccountsAndSlots(): DataAndSlot<SpotMarketAccount>[] {
454
- const map = this.spotMarketsSubscriber?.getAccountDataMap();
455
- return Array.from(map?.values() ?? []);
456
- }
457
-
458
- /** Returns the cached perp market, or undefined if `marketIndex` isn't tracked (or hasn't loaded yet). Does not throw `NotSubscribedError`. */
459
- getMarketAccountAndSlot(
460
- marketIndex: number
461
- ): DataAndSlot<PerpMarketAccount> | undefined {
462
- const accountPubkey =
463
- this.perpMarketIndexToAccountPubkeyMap.get(marketIndex);
464
- if (!accountPubkey) {
465
- return undefined;
466
- }
467
- return this.perpMarketsSubscriber?.getAccountData(accountPubkey);
468
- }
469
-
470
- /** Returns the cached spot market, or undefined if `marketIndex` isn't tracked (or hasn't loaded yet). Does not throw `NotSubscribedError`. */
471
- getSpotMarketAccountAndSlot(
472
- marketIndex: number
473
- ): DataAndSlot<SpotMarketAccount> | undefined {
474
- const accountPubkey =
475
- this.spotMarketIndexToAccountPubkeyMap.get(marketIndex);
476
- if (!accountPubkey) {
477
- return undefined;
478
- }
479
- return this.spotMarketsSubscriber?.getAccountData(accountPubkey);
480
- }
481
-
482
- /**
483
- * Looks up cached oracle price data by oracle id (see `getOracleId`), from this class's own
484
- * `oracleIdToOracleDataMap` rather than `oracleMultiSubscriber.getAccountData` — a single
485
- * oracle pubkey backing multiple oracle ids (e.g. shared price feeds) means the multi-subscriber's
486
- * own account map cannot be trusted to disambiguate them correctly.
487
- * @param oracleId Oracle id string from `getOracleId(publicKey, source)`.
488
- * @returns Cached price data/slot, or undefined if not tracked. Throws `NotSubscribedError` if not subscribed.
489
- */
490
- public getOraclePriceDataAndSlot(
491
- oracleId: string
492
- ): DataAndSlot<OraclePriceData> | undefined {
493
- this.assertIsSubscribed();
494
- // we need to rely on a map we store in this class because the grpcMultiAccountSubscriber does not track a mapping or oracle ID.
495
- // DO NOT call getAccountData on the oracleMultiSubscriber, it will not return the correct data in certain cases(BONK spot and perp market subscribed too at once).
496
- return this.oracleIdToOracleDataMap.get(oracleId);
497
- }
498
-
499
- /**
500
- * Convenience lookup: resolves the oracle price data currently mapped to a perp market's
501
- * oracle. If the cached market's oracle pubkey has drifted from (or isn't yet in)
502
- * `perpOracleMap`, triggers a background `setPerpOracleMap()` refresh and still returns the
503
- * (possibly stale) mapping for this call.
504
- * @param marketIndex Perp market index whose oracle price to look up.
505
- */
506
- public getOraclePriceDataAndSlotForPerpMarket(
507
- marketIndex: number
508
- ): DataAndSlot<OraclePriceData> | undefined {
509
- const perpMarketAccount = this.getMarketAccountAndSlot(marketIndex);
510
- const oracle = this.perpOracleMap.get(marketIndex);
511
- const oracleId = this.perpOracleStringMap.get(marketIndex);
512
- if (!perpMarketAccount || !oracleId) {
513
- return undefined;
514
- }
515
-
516
- if (!oracle || !perpMarketAccount.data.oracle.equals(oracle)) {
517
- // If the oracle has changed (or not yet cached), update the oracle map in background
518
- this.setPerpOracleMap();
519
- }
520
-
521
- return this.getOraclePriceDataAndSlot(oracleId);
522
- }
523
-
524
- /**
525
- * Convenience lookup: resolves the oracle price data currently mapped to a spot market's
526
- * oracle. If the cached market's oracle pubkey has drifted from (or isn't yet in)
527
- * `spotOracleMap`, triggers a background `setSpotOracleMap()` refresh and still returns the
528
- * (possibly stale) mapping for this call.
529
- * @param marketIndex Spot market index whose oracle price to look up.
530
- */
531
- public getOraclePriceDataAndSlotForSpotMarket(
532
- marketIndex: number
533
- ): DataAndSlot<OraclePriceData> | undefined {
534
- const spotMarketAccount = this.getSpotMarketAccountAndSlot(marketIndex);
535
- const oracle = this.spotOracleMap.get(marketIndex);
536
- const oracleId = this.spotOracleStringMap.get(marketIndex);
537
- if (!spotMarketAccount || !oracleId) {
538
- return undefined;
539
- }
540
-
541
- if (!oracle || !spotMarketAccount.data.oracle.equals(oracle)) {
542
- // If the oracle has changed (or not yet cached), update the oracle map in background
543
- this.setSpotOracleMap();
544
- }
545
-
546
- return this.getOraclePriceDataAndSlot(oracleId);
547
- }
548
-
549
- /** Rebuilds `perpOracleMap`/`perpOracleStringMap` from currently cached perp markets, calling `addOracle` for any oracle not yet tracked by `oracleMultiSubscriber`. */
550
- async setPerpOracleMap() {
551
- const perpMarketsMap = this.perpMarketsSubscriber?.getAccountDataMap();
552
- const perpMarkets = Array.from(perpMarketsMap?.values() ?? []);
553
- const addOraclePromises = [];
554
- for (const perpMarket of perpMarkets) {
555
- if (!perpMarket || !perpMarket.data) {
556
- continue;
557
- }
558
- const perpMarketAccount = perpMarket.data;
559
- const perpMarketIndex = perpMarketAccount.marketIndex;
560
- const oracle = perpMarketAccount.oracle;
561
- const oracleId = getOracleId(oracle, perpMarket.data.oracleSource);
562
- if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
563
- addOraclePromises.push(
564
- this.addOracle({
565
- publicKey: oracle,
566
- source: perpMarket.data.oracleSource,
567
- })
568
- );
569
- }
570
- this.perpOracleMap.set(perpMarketIndex, oracle);
571
- this.perpOracleStringMap.set(perpMarketIndex, oracleId);
572
- }
573
- await Promise.all(addOraclePromises);
574
- }
575
-
576
- /** Rebuilds `spotOracleMap`/`spotOracleStringMap` from currently cached spot markets, calling `addOracle` for any oracle not yet tracked by `oracleMultiSubscriber`. */
577
- async setSpotOracleMap() {
578
- const spotMarketsMap = this.spotMarketsSubscriber?.getAccountDataMap();
579
- const spotMarkets = Array.from(spotMarketsMap?.values() ?? []);
580
- const addOraclePromises = [];
581
- for (const spotMarket of spotMarkets) {
582
- if (!spotMarket || !spotMarket.data) {
583
- continue;
584
- }
585
- const spotMarketAccount = spotMarket.data;
586
- const spotMarketIndex = spotMarketAccount.marketIndex;
587
- const oracle = spotMarketAccount.oracle;
588
- const oracleId = getOracleId(oracle, spotMarketAccount.oracleSource);
589
- if (!this.oracleMultiSubscriber?.getAccountDataMap().has(oracleId)) {
590
- addOraclePromises.push(
591
- this.addOracle({
592
- publicKey: oracle,
593
- source: spotMarketAccount.oracleSource,
594
- })
595
- );
596
- }
597
- this.spotOracleMap.set(spotMarketIndex, oracle);
598
- this.spotOracleStringMap.set(spotMarketIndex, oracleId);
599
- }
600
- await Promise.all(addOraclePromises);
601
- }
602
-
603
- /**
604
- * Creates `perpMarketsSubscriber` (a `grpcMultiAccountSubscriber<PerpMarketAccount>`), seeds
605
- * it with `initialPerpMarketAccountData`, and subscribes it to every tracked perp market
606
- * pubkey in one gRPC stream. Registers an `onUnsubscribe` handler that automatically
607
- * re-invokes this method to resubscribe if the underlying stream drops.
608
- */
609
- async subscribeToPerpMarketAccounts(): Promise<boolean> {
610
- if (this.resubOpts?.logResubMessages) {
611
- console.log(
612
- '[grpcVelocityClientAccountSubscriberV2] subscribeToPerpMarketAccounts'
613
- );
614
- }
615
- const perpMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
616
- await Promise.all(
617
- this.perpMarketIndexes.map(async (marketIndex) => [
618
- marketIndex,
619
- await getPerpMarketPublicKey(this.program.programId, marketIndex),
620
- ])
621
- );
622
- for (const [
623
- marketIndex,
624
- accountPubkey,
625
- ] of perpMarketIndexToAccountPubkeys) {
626
- this.perpMarketIndexToAccountPubkeyMap.set(
627
- marketIndex,
628
- accountPubkey.toBase58()
629
- );
630
- }
631
-
632
- const perpMarketPubkeys = perpMarketIndexToAccountPubkeys.map(
633
- ([_, accountPubkey]) => accountPubkey
634
- );
635
-
636
- this.perpMarketsSubscriber =
637
- await grpcMultiAccountSubscriber.create<PerpMarketAccount>(
638
- this.grpcConfigs,
639
- 'perpMarket',
640
- this.program,
641
- undefined,
642
- this.resubOpts,
643
- undefined,
644
- async () => {
645
- try {
646
- if (this.resubOpts?.logResubMessages) {
647
- console.log(
648
- '[grpcVelocityClientAccountSubscriberV2] perp markets subscriber unsubscribed; resubscribing'
649
- );
650
- }
651
- await this.subscribeToPerpMarketAccounts();
652
- } catch (e) {
653
- console.error('Perp markets resubscribe failed:', e);
654
- }
655
- }
656
- );
657
-
658
- for (const data of this.initialPerpMarketAccountData.values()) {
659
- this.perpMarketsSubscriber.setAccountData(data.pubkey.toBase58(), data);
660
- }
661
-
662
- await this.perpMarketsSubscriber.subscribe(
663
- perpMarketPubkeys,
664
- (_accountId, data) => {
665
- this.eventEmitter.emit(
666
- 'perpMarketAccountUpdate',
667
- data as PerpMarketAccount
668
- );
669
- this.eventEmitter.emit('update');
670
- }
671
- );
672
-
673
- return true;
674
- }
675
-
676
- /**
677
- * Creates `spotMarketsSubscriber` (a `grpcMultiAccountSubscriber<SpotMarketAccount>`), seeds
678
- * it with `initialSpotMarketAccountData`, and subscribes it to every tracked spot market
679
- * pubkey in one gRPC stream. Registers an `onUnsubscribe` handler that automatically
680
- * re-invokes this method to resubscribe if the underlying stream drops.
681
- */
682
- async subscribeToSpotMarketAccounts(): Promise<boolean> {
683
- if (this.resubOpts?.logResubMessages) {
684
- console.log(
685
- '[grpcVelocityClientAccountSubscriberV2] subscribeToSpotMarketAccounts'
686
- );
687
- }
688
- const spotMarketIndexToAccountPubkeys: Array<[number, PublicKey]> =
689
- await Promise.all(
690
- this.spotMarketIndexes.map(async (marketIndex) => [
691
- marketIndex,
692
- await getSpotMarketPublicKey(this.program.programId, marketIndex),
693
- ])
694
- );
695
- for (const [
696
- marketIndex,
697
- accountPubkey,
698
- ] of spotMarketIndexToAccountPubkeys) {
699
- this.spotMarketIndexToAccountPubkeyMap.set(
700
- marketIndex,
701
- accountPubkey.toBase58()
702
- );
703
- }
704
-
705
- const spotMarketPubkeys = spotMarketIndexToAccountPubkeys.map(
706
- ([_, accountPubkey]) => accountPubkey
707
- );
708
-
709
- this.spotMarketsSubscriber =
710
- await grpcMultiAccountSubscriber.create<SpotMarketAccount>(
711
- this.grpcConfigs,
712
- 'spotMarket',
713
- this.program,
714
- undefined,
715
- this.resubOpts,
716
- undefined,
717
- async () => {
718
- try {
719
- if (this.resubOpts?.logResubMessages) {
720
- console.log(
721
- '[grpcVelocityClientAccountSubscriberV2] spot markets subscriber unsubscribed; resubscribing'
722
- );
723
- }
724
- await this.subscribeToSpotMarketAccounts();
725
- } catch (e) {
726
- console.error('Spot markets resubscribe failed:', e);
727
- }
728
- }
729
- );
730
-
731
- for (const data of this.initialSpotMarketAccountData.values()) {
732
- this.spotMarketsSubscriber.setAccountData(data.pubkey.toBase58(), data);
733
- }
734
-
735
- await this.spotMarketsSubscriber.subscribe(
736
- spotMarketPubkeys,
737
- (_accountId, data) => {
738
- this.eventEmitter.emit(
739
- 'spotMarketAccountUpdate',
740
- data as SpotMarketAccount
741
- );
742
- this.eventEmitter.emit('update');
743
- }
744
- );
745
-
746
- return true;
747
- }
748
-
749
- /**
750
- * Creates `oracleMultiSubscriber` (a `grpcMultiAccountSubscriber<OraclePriceData, OracleInfo>`)
751
- * and subscribes it to every distinct oracle pubkey in one gRPC stream, decoding buffers with
752
- * the source-appropriate `OracleClient`. Because multiple `(pubkey, source)` oracle ids can
753
- * share one pubkey, `oraclePubkeyToInfosMap` fans a single decode out to every matching
754
- * `OracleInfo`, and results are additionally indexed into `oracleIdToOracleDataMap` (by oracle
755
- * id, not pubkey) for `getOraclePriceDataAndSlot` to read. Registers an `onUnsubscribe` handler
756
- * that automatically re-invokes this method to resubscribe if the underlying stream drops.
757
- */
758
- async subscribeToOracles(): Promise<boolean> {
759
- if (this.resubOpts?.logResubMessages) {
760
- console.log('grpcVelocityClientAccountSubscriberV2 subscribeToOracles');
761
- }
762
- const oraclePubkeyToInfosMap = new Map<string, OracleInfo[]>();
763
- for (const info of this.oracleInfos) {
764
- const pubkey = info.publicKey.toBase58();
765
- if (!oraclePubkeyToInfosMap.has(pubkey)) {
766
- oraclePubkeyToInfosMap.set(pubkey, []);
767
- }
768
- oraclePubkeyToInfosMap.get(pubkey)!.push(info);
769
- }
770
-
771
- const oraclePubkeys = Array.from(
772
- new Set(this.oracleInfos.map((info) => info.publicKey))
773
- );
774
-
775
- this.oracleMultiSubscriber = await grpcMultiAccountSubscriber.create<
776
- OraclePriceData,
777
- OracleInfo
778
- >(
779
- this.grpcConfigs,
780
- 'oracle',
781
- this.program,
782
- (buffer: Buffer, pubkey?: string, accountProps?: OracleInfo) => {
783
- if (!pubkey) {
784
- throw new Error('Oracle pubkey missing in decode');
785
- }
786
-
787
- if (!accountProps) {
788
- throw new Error('Oracle accountProps missing in decode');
789
- }
790
-
791
- const client = this.oracleClientCache.get(
792
- accountProps.source,
793
- this.program.provider.connection,
794
- this.program
795
- );
796
- if (!client) {
797
- throw new Error('Oracle client missing in decode');
798
- }
799
- const price = client.getOraclePriceDataFromBuffer(buffer);
800
- return price;
801
- },
802
- this.resubOpts,
803
- undefined,
804
- async () => {
805
- try {
806
- if (this.resubOpts?.logResubMessages) {
807
- console.log(
808
- '[grpcVelocityClientAccountSubscriberV2] oracle subscriber unsubscribed; resubscribing'
809
- );
810
- }
811
- await this.subscribeToOracles();
812
- } catch (e) {
813
- console.error('Oracle resubscribe failed:', e);
814
- }
815
- },
816
- oraclePubkeyToInfosMap
817
- );
818
-
819
- for (const data of this.initialOraclePriceData.entries()) {
820
- const { publicKey } = getPublicKeyAndSourceFromOracleId(data[0]);
821
- this.oracleMultiSubscriber.setAccountData(publicKey.toBase58(), data[1]);
822
- this.oracleIdToOracleDataMap.set(data[0], {
823
- data: data[1],
824
- slot: 0,
825
- });
826
- }
827
-
828
- await this.oracleMultiSubscriber.subscribe(
829
- oraclePubkeys,
830
- (accountId, data, context, _b, accountProps) => {
831
- if (accountProps === undefined) {
832
- return;
833
- }
834
- const oracleId = getOracleId(accountId, accountProps.source);
835
- this.oracleIdToOracleDataMap.set(oracleId, {
836
- data,
837
- slot: context.slot,
838
- });
839
- this.eventEmitter.emit(
840
- 'oraclePriceUpdate',
841
- accountId,
842
- accountProps.source,
843
- data
844
- );
845
-
846
- this.eventEmitter.emit('update');
847
- }
848
- );
849
-
850
- return true;
851
- }
852
-
853
- /**
854
- * Applies `delistedMarketSetting` to any perp market currently `status: delisted` (and its
855
- * oracle, if not shared with a live spot market) by removing its pubkey from the relevant
856
- * `grpcMultiAccountSubscriber` via `removeAccounts`. A no-op if the setting is `Subscribe`.
857
- * Unlike the WebSocket/polling variants, this does not distinguish `Unsubscribe` from
858
- * `Discard` — both remove the account from the multiplexed stream (there is no per-account
859
- * "keep last known data, stop streaming" state to preserve at this granularity).
860
- */
861
- async handleDelistedMarkets(): Promise<void> {
862
- if (this.delistedMarketSetting === DelistedMarketSetting.Subscribe) {
863
- return;
864
- }
865
-
866
- const { perpMarketIndexes, oracles } = findDelistedPerpMarketsAndOracles(
867
- Array.from(
868
- this.perpMarketsSubscriber?.getAccountDataMap().values() || []
869
- ),
870
- Array.from(this.spotMarketsSubscriber?.getAccountDataMap().values() || [])
871
- );
872
-
873
- // Build array of perp market pubkeys to remove
874
- const perpMarketPubkeysToRemove = perpMarketIndexes
875
- .map((marketIndex) => {
876
- const pubkeyString =
877
- this.perpMarketIndexToAccountPubkeyMap.get(marketIndex);
878
- return pubkeyString ? new PublicKey(pubkeyString) : null;
879
- })
880
- .filter((pubkey): pubkey is PublicKey => pubkey !== null);
881
-
882
- // Build array of oracle pubkeys to remove
883
- const oraclePubkeysToRemove = oracles.map((oracle) => oracle.publicKey);
884
-
885
- // Remove accounts in batches - perp markets
886
- if (perpMarketPubkeysToRemove.length > 0) {
887
- await this.perpMarketsSubscriber?.removeAccounts(
888
- perpMarketPubkeysToRemove
889
- );
890
- }
891
-
892
- // Remove accounts in batches - oracles
893
- if (oraclePubkeysToRemove.length > 0) {
894
- await this.oracleMultiSubscriber?.removeAccounts(oraclePubkeysToRemove);
895
- }
896
- }
897
-
898
- /** Clears the seed data stashed by `setInitialData()` once the multi-account subscribers have consumed it, freeing the memory. */
899
- removeInitialData() {
900
- this.initialPerpMarketAccountData = new Map();
901
- this.initialSpotMarketAccountData = new Map();
902
- this.initialOraclePriceData = new Map();
903
- }
904
-
905
- /** Tears down `oracleMultiSubscriber`'s gRPC stream, if active, and clears the reference. */
906
- async unsubscribeFromOracles(): Promise<void> {
907
- if (this.oracleMultiSubscriber) {
908
- await this.oracleMultiSubscriber.unsubscribe();
909
- this.oracleMultiSubscriber = undefined;
910
- return;
911
- }
912
- }
913
-
914
- /**
915
- * Tears down the `State` subscriber and all three multiplexed subscribers (perp markets, spot
916
- * markets, oracles), then clears every internal map to avoid holding stale references. A no-op
917
- * if not subscribed.
918
- */
919
- async unsubscribe(): Promise<void> {
920
- if (!this.isSubscribed) {
921
- return;
922
- }
923
-
924
- this.isSubscribed = false;
925
- this.isSubscribing = false;
926
-
927
- await this.stateAccountSubscriber?.unsubscribe();
928
- await this.unsubscribeFromOracles();
929
- await this.perpMarketsSubscriber?.unsubscribe();
930
- await this.spotMarketsSubscriber?.unsubscribe();
931
-
932
- // Clean up all maps to prevent memory leaks
933
- this.perpMarketIndexToAccountPubkeyMap.clear();
934
- this.spotMarketIndexToAccountPubkeyMap.clear();
935
- this.oracleIdToOracleDataMap.clear();
936
- this.perpOracleMap.clear();
937
- this.perpOracleStringMap.clear();
938
- this.spotOracleMap.clear();
939
- this.spotOracleStringMap.clear();
940
- }
941
- }