@drift-labs/sdk 0.1.29 → 0.1.30-master.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.
Files changed (85) hide show
  1. package/lib/accounts/bulkAccountLoader.d.ts +2 -1
  2. package/lib/accounts/bulkAccountLoader.js +44 -32
  3. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +15 -0
  4. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +50 -23
  5. package/lib/clearingHouse.d.ts +0 -1
  6. package/lib/clearingHouse.js +0 -14
  7. package/lib/constants/accounts.d.ts +15 -0
  8. package/lib/constants/accounts.js +18 -0
  9. package/lib/factory/clearingHouse.d.ts +14 -4
  10. package/lib/factory/clearingHouse.js +23 -6
  11. package/lib/idl/clearing_house.json +2 -33
  12. package/lib/index.d.ts +0 -1
  13. package/lib/index.js +0 -1
  14. package/lib/math/amm.d.ts +20 -0
  15. package/lib/math/amm.js +151 -1
  16. package/lib/tx/retryTxSender.d.ts +5 -2
  17. package/lib/tx/retryTxSender.js +14 -1
  18. package/lib/util/promiseTimeout.d.ts +1 -0
  19. package/lib/util/promiseTimeout.js +14 -0
  20. package/package.json +1 -1
  21. package/src/accounts/bulkAccountLoader.ts +51 -40
  22. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +73 -30
  23. package/src/clearingHouse.ts +0 -13
  24. package/src/constants/accounts.ts +26 -0
  25. package/src/factory/clearingHouse.ts +47 -7
  26. package/src/idl/clearing_house.json +2 -33
  27. package/src/index.ts +0 -1
  28. package/src/math/amm.ts +212 -1
  29. package/src/tx/retryTxSender.ts +19 -1
  30. package/src/util/promiseTimeout.ts +14 -0
  31. package/lib/math/repeg.d.ts +0 -32
  32. package/lib/math/repeg.js +0 -178
  33. package/src/accounts/pollingClearingHouseAccountSubscriber.js +0 -210
  34. package/src/accounts/pollingClearingHouseAccountSubscriber.js.map +0 -1
  35. package/src/accounts/pollingOracleSubscriber.js +0 -65
  36. package/src/accounts/pollingOracleSubscriber.js.map +0 -1
  37. package/src/accounts/pollingTokenAccountSubscriber.js +0 -65
  38. package/src/accounts/pollingTokenAccountSubscriber.js.map +0 -1
  39. package/src/accounts/utils.js +0 -8
  40. package/src/accounts/utils.js.map +0 -1
  41. package/src/accounts/webSocketAccountSubscriber.js +0 -64
  42. package/src/accounts/webSocketAccountSubscriber.js.map +0 -1
  43. package/src/accounts/webSocketClearingHouseAccountSubscriber.js +0 -212
  44. package/src/accounts/webSocketClearingHouseAccountSubscriber.js.map +0 -1
  45. package/src/assert/assert.js +0 -10
  46. package/src/assert/assert.js.map +0 -1
  47. package/src/constants/markets.js +0 -167
  48. package/src/constants/markets.js.map +0 -1
  49. package/src/constants/numericConstants.js +0 -22
  50. package/src/constants/numericConstants.js.map +0 -1
  51. package/src/math/conversion.js +0 -16
  52. package/src/math/conversion.js.map +0 -1
  53. package/src/math/funding.js +0 -223
  54. package/src/math/funding.js.map +0 -1
  55. package/src/math/insuranceFund.js +0 -23
  56. package/src/math/insuranceFund.js.map +0 -1
  57. package/src/math/market.js +0 -30
  58. package/src/math/market.js.map +0 -1
  59. package/src/math/orders.js +0 -73
  60. package/src/math/orders.js.map +0 -1
  61. package/src/math/position.js +0 -121
  62. package/src/math/position.js.map +0 -1
  63. package/src/math/repeg.ts +0 -253
  64. package/src/math/trade.js +0 -182
  65. package/src/math/trade.js.map +0 -1
  66. package/src/math/utils.js +0 -27
  67. package/src/math/utils.js.map +0 -1
  68. package/src/oracles/switchboardClient.js +0 -60
  69. package/src/oracles/switchboardClient.js.map +0 -1
  70. package/src/oracles/types.js +0 -3
  71. package/src/oracles/types.js.map +0 -1
  72. package/src/token/index.js +0 -39
  73. package/src/token/index.js.map +0 -1
  74. package/src/tx/defaultTxSender.js +0 -13
  75. package/src/tx/defaultTxSender.js.map +0 -1
  76. package/src/tx/retryTxSender.js +0 -137
  77. package/src/tx/retryTxSender.js.map +0 -1
  78. package/src/tx/types.js +0 -3
  79. package/src/tx/types.js.map +0 -1
  80. package/src/tx/utils.js +0 -9
  81. package/src/tx/utils.js.map +0 -1
  82. package/src/util/computeUnits.js +0 -17
  83. package/src/util/computeUnits.js.map +0 -1
  84. package/src/util/tps.js +0 -17
  85. package/src/util/tps.js.map +0 -1
package/lib/math/amm.d.ts CHANGED
@@ -38,6 +38,24 @@ export declare function calculateSwapOutput(inputAssetReserve: BN, swapAmount: B
38
38
  * @param positionDirection
39
39
  */
40
40
  export declare function getSwapDirection(inputAssetType: AssetType, positionDirection: PositionDirection): SwapDirection;
41
+ /**
42
+ * Helper function calculating adjust k cost
43
+ * @param market
44
+ * @param marketIndex
45
+ * @param numerator
46
+ * @param denomenator
47
+ * @returns cost : Precision QUOTE_ASSET_PRECISION
48
+ */
49
+ export declare function calculateAdjustKCost(market: Market, marketIndex: BN, numerator: BN, denomenator: BN): BN;
50
+ /**
51
+ * Helper function calculating adjust pegMultiplier (repeg) cost
52
+ *
53
+ * @param market
54
+ * @param marketIndex
55
+ * @param newPeg
56
+ * @returns cost : Precision QUOTE_ASSET_PRECISION
57
+ */
58
+ export declare function calculateRepegCost(market: Market, marketIndex: BN, newPeg: BN): BN;
41
59
  /**
42
60
  * Helper function calculating terminal price of amm
43
61
  *
@@ -46,3 +64,5 @@ export declare function getSwapDirection(inputAssetType: AssetType, positionDire
46
64
  */
47
65
  export declare function calculateTerminalPrice(market: Market): BN;
48
66
  export declare function calculateMaxBaseAssetAmountToTrade(amm: AMM, limit_price: BN): [BN, PositionDirection];
67
+ export declare function calculateBudgetedK(market: Market, cost: BN): [BN, BN];
68
+ export declare function calculateBudgetedPeg(market: Market, cost: BN): BN;
package/lib/math/amm.js CHANGED
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = void 0;
3
+ exports.calculateBudgetedPeg = exports.calculateBudgetedK = exports.calculateMaxBaseAssetAmountToTrade = exports.calculateTerminalPrice = exports.calculateRepegCost = exports.calculateAdjustKCost = exports.getSwapDirection = exports.calculateSwapOutput = exports.calculateAmmReservesAfterSwap = exports.calculatePrice = void 0;
4
4
  const anchor_1 = require("@project-serum/anchor");
5
5
  const numericConstants_1 = require("../constants/numericConstants");
6
+ const position_1 = require("./position");
6
7
  const types_1 = require("../types");
7
8
  const assert_1 = require("../assert/assert");
8
9
  const __1 = require("..");
@@ -87,6 +88,92 @@ function getSwapDirection(inputAssetType, positionDirection) {
87
88
  return types_1.SwapDirection.ADD;
88
89
  }
89
90
  exports.getSwapDirection = getSwapDirection;
91
+ /**
92
+ * Helper function calculating adjust k cost
93
+ * @param market
94
+ * @param marketIndex
95
+ * @param numerator
96
+ * @param denomenator
97
+ * @returns cost : Precision QUOTE_ASSET_PRECISION
98
+ */
99
+ function calculateAdjustKCost(market, marketIndex, numerator, denomenator) {
100
+ const netUserPosition = {
101
+ baseAssetAmount: market.baseAssetAmount,
102
+ lastCumulativeFundingRate: market.amm.cumulativeFundingRate,
103
+ marketIndex: new anchor_1.BN(marketIndex),
104
+ quoteAssetAmount: new anchor_1.BN(0),
105
+ openOrders: new anchor_1.BN(0),
106
+ };
107
+ const currentValue = (0, position_1.calculateBaseAssetValue)(market, netUserPosition);
108
+ const marketNewK = Object.assign({}, market);
109
+ marketNewK.amm = Object.assign({}, market.amm);
110
+ marketNewK.amm.baseAssetReserve = market.amm.baseAssetReserve
111
+ .mul(numerator)
112
+ .div(denomenator);
113
+ marketNewK.amm.quoteAssetReserve = market.amm.quoteAssetReserve
114
+ .mul(numerator)
115
+ .div(denomenator);
116
+ marketNewK.amm.sqrtK = market.amm.sqrtK.mul(numerator).div(denomenator);
117
+ netUserPosition.quoteAssetAmount = currentValue;
118
+ const cost = (0, __1.calculatePositionPNL)(marketNewK, netUserPosition);
119
+ const p = numericConstants_1.PEG_PRECISION.mul(numerator).div(denomenator);
120
+ const x = market.amm.baseAssetReserve;
121
+ const y = market.amm.quoteAssetReserve;
122
+ const delta = market.baseAssetAmount;
123
+ const k = market.amm.sqrtK.mul(market.amm.sqrtK);
124
+ const numer1 = numericConstants_1.PEG_PRECISION.sub(p).mul(y).div(numericConstants_1.PEG_PRECISION);
125
+ const numer20 = k
126
+ .mul(p)
127
+ .mul(p)
128
+ .div(numericConstants_1.PEG_PRECISION)
129
+ .div(numericConstants_1.PEG_PRECISION)
130
+ .div(x.mul(p).div(numericConstants_1.PEG_PRECISION).add(delta));
131
+ const numer21 = k.div(x.add(delta));
132
+ const formulaCost = numer21
133
+ .sub(numer20)
134
+ .sub(numer1)
135
+ .mul(market.amm.pegMultiplier)
136
+ .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
137
+ console.log((0, __1.convertToNumber)(formulaCost, numericConstants_1.QUOTE_PRECISION));
138
+ // p.div(p.mul(x).add(delta)).sub()
139
+ return cost;
140
+ }
141
+ exports.calculateAdjustKCost = calculateAdjustKCost;
142
+ /**
143
+ * Helper function calculating adjust pegMultiplier (repeg) cost
144
+ *
145
+ * @param market
146
+ * @param marketIndex
147
+ * @param newPeg
148
+ * @returns cost : Precision QUOTE_ASSET_PRECISION
149
+ */
150
+ function calculateRepegCost(market, marketIndex, newPeg) {
151
+ const netUserPosition = {
152
+ baseAssetAmount: market.baseAssetAmount,
153
+ lastCumulativeFundingRate: market.amm.cumulativeFundingRate,
154
+ marketIndex: new anchor_1.BN(marketIndex),
155
+ quoteAssetAmount: new anchor_1.BN(0),
156
+ openOrders: new anchor_1.BN(0),
157
+ };
158
+ const currentValue = (0, position_1.calculateBaseAssetValue)(market, netUserPosition);
159
+ netUserPosition.quoteAssetAmount = currentValue;
160
+ const prevMarketPrice = (0, __1.calculateMarkPrice)(market);
161
+ const marketNewPeg = Object.assign({}, market);
162
+ marketNewPeg.amm = Object.assign({}, market.amm);
163
+ // const marketNewPeg = JSON.parse(JSON.stringify(market));
164
+ marketNewPeg.amm.pegMultiplier = newPeg;
165
+ console.log('Price moves from', (0, __1.convertToNumber)(prevMarketPrice), 'to', (0, __1.convertToNumber)((0, __1.calculateMarkPrice)(marketNewPeg)));
166
+ const cost = (0, __1.calculatePositionPNL)(marketNewPeg, netUserPosition);
167
+ const k = market.amm.sqrtK.mul(market.amm.sqrtK);
168
+ const newQuoteAssetReserve = k.div(market.amm.baseAssetReserve.add(netUserPosition.baseAssetAmount));
169
+ const deltaQuoteAssetReserves = newQuoteAssetReserve.sub(market.amm.quoteAssetReserve);
170
+ const cost2 = deltaQuoteAssetReserves
171
+ .mul(market.amm.pegMultiplier.sub(newPeg))
172
+ .div(numericConstants_1.AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
173
+ console.log((0, __1.convertToNumber)(cost2, numericConstants_1.QUOTE_PRECISION));
174
+ return cost;
175
+ }
176
+ exports.calculateRepegCost = calculateRepegCost;
90
177
  /**
91
178
  * Helper function calculating terminal price of amm
92
179
  *
@@ -132,3 +219,66 @@ function calculateMaxBaseAssetAmountToTrade(amm, limit_price) {
132
219
  }
133
220
  }
134
221
  exports.calculateMaxBaseAssetAmountToTrade = calculateMaxBaseAssetAmountToTrade;
222
+ function calculateBudgetedK(market, cost) {
223
+ // wolframalpha.com
224
+ // (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
225
+ // p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
226
+ // todo: assumes k = x * y
227
+ // otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
228
+ // const k = market.amm.sqrtK.mul(market.amm.sqrtK);
229
+ const x = market.amm.baseAssetReserve;
230
+ const y = market.amm.quoteAssetReserve;
231
+ const d = market.baseAssetAmount;
232
+ const Q = market.amm.pegMultiplier;
233
+ const C = cost.mul(new anchor_1.BN(-1));
234
+ const numer1 = y.mul(d).mul(Q).div(numericConstants_1.AMM_RESERVE_PRECISION).div(numericConstants_1.PEG_PRECISION);
235
+ const numer2 = C.mul(x.add(d)).div(numericConstants_1.QUOTE_PRECISION);
236
+ const denom1 = C.mul(x)
237
+ .mul(x.add(d))
238
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
239
+ .div(numericConstants_1.QUOTE_PRECISION);
240
+ const denom2 = y
241
+ .mul(d)
242
+ .mul(d)
243
+ .mul(Q)
244
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
245
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
246
+ .div(numericConstants_1.PEG_PRECISION);
247
+ const numerator = d
248
+ .mul(numer1.add(numer2))
249
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
250
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
251
+ .div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
252
+ const denominator = denom1
253
+ .add(denom2)
254
+ .div(numericConstants_1.AMM_RESERVE_PRECISION)
255
+ .div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO);
256
+ console.log(numerator, denominator);
257
+ // const p = (numerator).div(denominator);
258
+ // const formulaCost = (numer21.sub(numer20).sub(numer1)).mul(market.amm.pegMultiplier).div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
259
+ // console.log(convertToNumber(formulaCost, QUOTE_PRECISION))
260
+ return [numerator, denominator];
261
+ }
262
+ exports.calculateBudgetedK = calculateBudgetedK;
263
+ function calculateBudgetedPeg(market, cost) {
264
+ // wolframalpha.com
265
+ // (1/(x+d) - p/(x*p+d))*y*d*Q = C solve for p
266
+ // p = (d(y*d*Q - C(x+d))) / (C*x(x+d) + y*y*d*Q)
267
+ // todo: assumes k = x * y
268
+ // otherwise use: (y(1-p) + (kp^2/(x*p+d)) - k/(x+d)) * Q = C solve for p
269
+ const k = market.amm.sqrtK.mul(market.amm.sqrtK);
270
+ const x = market.amm.baseAssetReserve;
271
+ const y = market.amm.quoteAssetReserve;
272
+ const d = market.baseAssetAmount;
273
+ const Q = market.amm.pegMultiplier;
274
+ const C = cost.mul(new anchor_1.BN(-1));
275
+ const deltaQuoteAssetReserves = y.sub(k.div(x.add(d)));
276
+ const deltaPegMultiplier = C.mul(numericConstants_1.MARK_PRICE_PRECISION)
277
+ .div(deltaQuoteAssetReserves.div(numericConstants_1.AMM_TO_QUOTE_PRECISION_RATIO))
278
+ .mul(numericConstants_1.PEG_PRECISION)
279
+ .div(numericConstants_1.QUOTE_PRECISION);
280
+ console.log(Q.toNumber(), 'change by', deltaPegMultiplier.toNumber() / numericConstants_1.MARK_PRICE_PRECISION.toNumber());
281
+ const newPeg = Q.sub(deltaPegMultiplier.mul(numericConstants_1.PEG_PRECISION).div(numericConstants_1.MARK_PRICE_PRECISION));
282
+ return newPeg;
283
+ }
284
+ exports.calculateBudgetedPeg = calculateBudgetedPeg;
@@ -1,5 +1,6 @@
1
+ /// <reference types="node" />
1
2
  import { TxSender } from './types';
2
- import { Commitment, ConfirmOptions, RpcResponseAndContext, Signer, SignatureResult, Transaction, TransactionSignature } from '@solana/web3.js';
3
+ import { Commitment, ConfirmOptions, RpcResponseAndContext, Signer, SignatureResult, Transaction, TransactionSignature, Connection } from '@solana/web3.js';
3
4
  import { Provider } from '@project-serum/anchor';
4
5
  declare type ResolveReference = {
5
6
  resolve?: () => void;
@@ -8,12 +9,14 @@ export declare class RetryTxSender implements TxSender {
8
9
  provider: Provider;
9
10
  timeout: number;
10
11
  retrySleep: number;
11
- constructor(provider: Provider, timeout?: number, retrySleep?: number);
12
+ additionalConnections: Connection[];
13
+ constructor(provider: Provider, timeout?: number, retrySleep?: number, additionalConnections?: Connection[]);
12
14
  send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<TransactionSignature>;
13
15
  prepareTx(tx: Transaction, additionalSigners: Array<Signer>, opts: ConfirmOptions): Promise<Transaction>;
14
16
  confirmTransaction(signature: TransactionSignature, commitment?: Commitment): Promise<RpcResponseAndContext<SignatureResult>>;
15
17
  getTimestamp(): number;
16
18
  sleep(reference: ResolveReference): Promise<void>;
17
19
  promiseTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null>;
20
+ sendToAdditionalConnections(rawTx: Buffer, opts: ConfirmOptions): void;
18
21
  }
19
22
  export {};
@@ -18,10 +18,11 @@ const bs58_1 = __importDefault(require("bs58"));
18
18
  const DEFAULT_TIMEOUT = 35000;
19
19
  const DEFAULT_RETRY = 8000;
20
20
  class RetryTxSender {
21
- constructor(provider, timeout, retrySleep) {
21
+ constructor(provider, timeout, retrySleep, additionalConnections = new Array()) {
22
22
  this.provider = provider;
23
23
  this.timeout = timeout !== null && timeout !== void 0 ? timeout : DEFAULT_TIMEOUT;
24
24
  this.retrySleep = retrySleep !== null && retrySleep !== void 0 ? retrySleep : DEFAULT_RETRY;
25
+ this.additionalConnections = additionalConnections;
25
26
  }
26
27
  send(tx, additionalSigners, opts) {
27
28
  return __awaiter(this, void 0, void 0, function* () {
@@ -35,6 +36,7 @@ class RetryTxSender {
35
36
  const rawTransaction = tx.serialize();
36
37
  const startTime = this.getTimestamp();
37
38
  const txid = yield this.provider.connection.sendRawTransaction(rawTransaction, opts);
39
+ this.sendToAdditionalConnections(rawTransaction, opts);
38
40
  let done = false;
39
41
  const resolveReference = {
40
42
  resolve: undefined,
@@ -55,6 +57,7 @@ class RetryTxSender {
55
57
  console.error(e);
56
58
  stopWaiting();
57
59
  });
60
+ this.sendToAdditionalConnections(rawTransaction, opts);
58
61
  }
59
62
  }
60
63
  }))();
@@ -149,5 +152,15 @@ class RetryTxSender {
149
152
  return result;
150
153
  });
151
154
  }
155
+ sendToAdditionalConnections(rawTx, opts) {
156
+ this.additionalConnections.map((connection) => {
157
+ connection.sendRawTransaction(rawTx, opts).catch((e) => {
158
+ console.error(
159
+ // @ts-ignore
160
+ `error sending tx to additional connection ${connection._rpcEndpoint}`);
161
+ console.error(e);
162
+ });
163
+ });
164
+ }
152
165
  }
153
166
  exports.RetryTxSender = RetryTxSender;
@@ -0,0 +1 @@
1
+ export declare function promiseTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null>;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseTimeout = void 0;
4
+ function promiseTimeout(promise, timeoutMs) {
5
+ let timeoutId;
6
+ const timeoutPromise = new Promise((resolve) => {
7
+ timeoutId = setTimeout(() => resolve(null), timeoutMs);
8
+ });
9
+ return Promise.race([promise, timeoutPromise]).then((result) => {
10
+ clearTimeout(timeoutId);
11
+ return result;
12
+ });
13
+ }
14
+ exports.promiseTimeout = promiseTimeout;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "0.1.29",
3
+ "version": "0.1.30-master.2",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -1,6 +1,7 @@
1
1
  import { Commitment, Connection, PublicKey } from '@solana/web3.js';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { AccountData } from './types';
4
+ import { promiseTimeout } from '../util/promiseTimeout';
4
5
 
5
6
  type AccountToLoad = {
6
7
  publicKey: PublicKey;
@@ -10,7 +11,6 @@ type AccountToLoad = {
10
11
  const GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE = 99;
11
12
 
12
13
  const oneMinute = 60 * 1000;
13
- const fiveMinutes = 5 * 60 * 1000;
14
14
 
15
15
  export class BulkAccountLoader {
16
16
  connection: Connection;
@@ -24,7 +24,7 @@ export class BulkAccountLoader {
24
24
  loadPromise?: Promise<void>;
25
25
  loadPromiseResolver: () => void;
26
26
  loggingEnabled = false;
27
- lastUpdate = Date.now();
27
+ lastTimeLoadingPromiseCleared = Date.now();
28
28
 
29
29
  public constructor(
30
30
  connection: Connection,
@@ -43,10 +43,15 @@ export class BulkAccountLoader {
43
43
  const existingSize = this.accountsToLoad.size;
44
44
 
45
45
  const callbackId = uuidv4();
46
+ this.log(
47
+ `Adding account ${publicKey.toString()} callback id ${callbackId}`
48
+ );
46
49
  const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString());
47
50
  if (existingAccountToLoad) {
51
+ this.log(`account already exists`);
48
52
  existingAccountToLoad.callbacks.set(callbackId, callback);
49
53
  } else {
54
+ this.log(`account doesn't already exist. creating new callback map`);
50
55
  const callbacks = new Map<string, (buffer: Buffer) => void>();
51
56
  callbacks.set(callbackId, callback);
52
57
  const newAccountToLoad = {
@@ -67,6 +72,9 @@ export class BulkAccountLoader {
67
72
  }
68
73
 
69
74
  public removeAccount(publicKey: PublicKey, callbackId: string): void {
75
+ this.log(
76
+ `Removing account ${publicKey.toString()} callback id ${callbackId}`
77
+ );
70
78
  const existingAccountToLoad = this.accountsToLoad.get(publicKey.toString());
71
79
  if (existingAccountToLoad) {
72
80
  existingAccountToLoad.callbacks.delete(callbackId);
@@ -99,17 +107,29 @@ export class BulkAccountLoader {
99
107
 
100
108
  public async load(): Promise<void> {
101
109
  if (this.loadPromise) {
102
- return this.loadPromise;
110
+ const now = Date.now();
111
+ if (now - this.lastTimeLoadingPromiseCleared > oneMinute) {
112
+ this.log(`Load promise hasnt been clearing for one minute. Clearing.`);
113
+ this.loadPromise = undefined;
114
+ } else {
115
+ this.log(`Load promise exists. Returning early`);
116
+ return this.loadPromise;
117
+ }
103
118
  }
119
+
104
120
  this.loadPromise = new Promise((resolver) => {
105
121
  this.loadPromiseResolver = resolver;
106
122
  });
123
+ this.lastTimeLoadingPromiseCleared = Date.now();
124
+
125
+ this.log(`Loading`);
107
126
 
108
127
  try {
109
128
  const chunks = this.chunks(
110
129
  Array.from(this.accountsToLoad.values()),
111
130
  GET_MULTIPLE_ACCOUNTS_CHUNK_SIZE
112
131
  );
132
+ this.log(`${chunks.length} chunks`);
113
133
 
114
134
  await Promise.all(
115
135
  chunks.map((chunk) => {
@@ -122,28 +142,17 @@ export class BulkAccountLoader {
122
142
  for (const [_, callback] of this.errorCallbacks) {
123
143
  callback(e);
124
144
  }
145
+ this.log('finished error callbacks');
125
146
  } finally {
147
+ this.log(`resetting load promise`);
126
148
  this.loadPromiseResolver();
127
149
  this.loadPromise = undefined;
128
-
129
- const now = Date.now();
130
- if (now - this.lastUpdate > fiveMinutes) {
131
- if (this.loggingEnabled) {
132
- console.log(
133
- "Haven't seen updated account in five minutes. Bulk account loader creating new Connection Object"
134
- );
135
- }
136
- this.connection = new Connection(
137
- // @ts-ignore
138
- this.connection._rpcEndpoint,
139
- this.connection.commitment
140
- );
141
- }
142
150
  }
143
151
  }
144
152
 
145
153
  async loadChunk(accountsToLoad: AccountToLoad[]): Promise<void> {
146
154
  if (accountsToLoad.length === 0) {
155
+ this.log(`no accounts in chunk`);
147
156
  return;
148
157
  }
149
158
 
@@ -154,15 +163,15 @@ export class BulkAccountLoader {
154
163
  { commitment: this.commitment },
155
164
  ];
156
165
 
157
- // @ts-ignore
158
- const rpcResponse = await this.connection._rpcRequest(
159
- 'getMultipleAccounts',
160
- args
166
+ const rpcResponse: any | null = await promiseTimeout(
167
+ // @ts-ignore
168
+ this.connection._rpcRequest('getMultipleAccounts', args),
169
+ 10 * 1000 // 30 second timeout
161
170
  );
162
171
 
163
- const oneMinuteSinceLastUpdate = Date.now() - this.lastUpdate > oneMinute;
164
- if (this.loggingEnabled && oneMinuteSinceLastUpdate) {
165
- console.log('rpcResponse', JSON.stringify(rpcResponse));
172
+ if (rpcResponse === null) {
173
+ this.log('request to rpc timed out', true);
174
+ return;
166
175
  }
167
176
 
168
177
  const newSlot = rpcResponse.result.context.slot;
@@ -179,44 +188,44 @@ export class BulkAccountLoader {
179
188
  newBuffer = Buffer.from(raw, dataType);
180
189
  }
181
190
 
182
- if (this.loggingEnabled && oneMinuteSinceLastUpdate) {
183
- console.log('oldRPCResponse', oldRPCResponse);
184
- }
185
-
186
191
  if (!oldRPCResponse) {
192
+ this.log('No old rpc response, updating account data');
187
193
  this.accountData.set(key, {
188
194
  slot: newSlot,
189
195
  buffer: newBuffer,
190
196
  });
191
197
  this.handleAccountCallbacks(accountToLoad, newBuffer);
192
- this.lastUpdate = Date.now();
193
198
  continue;
194
199
  }
195
200
 
196
201
  if (newSlot <= oldRPCResponse.slot) {
202
+ this.log(`new slot ${newSlot} old slot ${oldRPCResponse.slot}`);
197
203
  continue;
198
204
  }
199
205
 
200
206
  const oldBuffer = oldRPCResponse.buffer;
201
207
  if (newBuffer && (!oldBuffer || !newBuffer.equals(oldBuffer))) {
208
+ this.log('new buffer, updating account data');
202
209
  this.accountData.set(key, {
203
210
  slot: newSlot,
204
211
  buffer: newBuffer,
205
212
  });
206
213
  this.handleAccountCallbacks(accountToLoad, newBuffer);
207
- this.lastUpdate = Date.now();
208
- } else if (this.loggingEnabled) {
209
- console.log('unable to update account for newest slot');
210
- console.log('oldBuffer', oldBuffer);
211
- console.log('newBuffer', newBuffer);
214
+ } else {
215
+ this.log('unable to update account for newest slot');
216
+ this.log('oldBuffer ' + oldBuffer);
217
+ this.log('newBuffer ' + newBuffer);
218
+ this.log('buffers equal ' + newBuffer?.equals(oldBuffer));
212
219
  }
213
220
  }
214
221
  }
215
222
 
216
223
  handleAccountCallbacks(accountToLoad: AccountToLoad, buffer: Buffer): void {
224
+ this.log('handling account callbacks');
217
225
  for (const [_, callback] of accountToLoad.callbacks) {
218
226
  callback(buffer);
219
227
  }
228
+ this.log('finished account callbacks');
220
229
  }
221
230
 
222
231
  public getAccountData(publicKey: PublicKey): Buffer | undefined {
@@ -229,9 +238,7 @@ export class BulkAccountLoader {
229
238
  return;
230
239
  }
231
240
 
232
- if (this.loggingEnabled) {
233
- console.log(`startPolling`);
234
- }
241
+ this.log('startPolling');
235
242
 
236
243
  this.intervalId = setInterval(this.load.bind(this), this.pollingFrequency);
237
244
  }
@@ -241,9 +248,13 @@ export class BulkAccountLoader {
241
248
  clearInterval(this.intervalId);
242
249
  this.intervalId = undefined;
243
250
 
244
- if (this.loggingEnabled) {
245
- console.log(`stopPolling`);
246
- }
251
+ this.log('stopPolling');
252
+ }
253
+ }
254
+
255
+ public log(msg: string, force = false): void {
256
+ if (this.loggingEnabled || force) {
257
+ console.log(msg);
247
258
  }
248
259
  }
249
260
  }