@drift-labs/sdk 2.54.0-beta.0 → 2.54.0-beta.10

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 (52) hide show
  1. package/VERSION +1 -1
  2. package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +0 -1
  3. package/lib/constants/perpMarkets.js +20 -0
  4. package/lib/driftClient.d.ts +8 -2
  5. package/lib/driftClient.js +95 -20
  6. package/lib/events/webSocketLogProvider.js +3 -0
  7. package/lib/idl/drift.json +7 -1
  8. package/lib/jupiter/jupiterClient.d.ts +6 -0
  9. package/lib/jupiter/jupiterClient.js +2 -2
  10. package/lib/math/funding.js +24 -1
  11. package/lib/math/oracles.js +2 -2
  12. package/lib/math/superStake.d.ts +51 -0
  13. package/lib/math/superStake.js +10 -2
  14. package/lib/priorityFee/averageOverSlotsStrategy.d.ts +0 -5
  15. package/lib/priorityFee/averageOverSlotsStrategy.js +1 -13
  16. package/lib/priorityFee/maxOverSlotsStrategy.d.ts +0 -5
  17. package/lib/priorityFee/maxOverSlotsStrategy.js +1 -13
  18. package/lib/priorityFee/priorityFeeSubscriber.d.ts +5 -4
  19. package/lib/priorityFee/priorityFeeSubscriber.js +15 -21
  20. package/lib/tx/baseTxSender.d.ts +6 -2
  21. package/lib/tx/baseTxSender.js +47 -2
  22. package/lib/tx/fastSingleTxSender.d.ts +3 -2
  23. package/lib/tx/fastSingleTxSender.js +10 -2
  24. package/lib/tx/retryTxSender.d.ts +3 -2
  25. package/lib/tx/retryTxSender.js +14 -23
  26. package/lib/tx/types.d.ts +5 -0
  27. package/lib/tx/types.js +7 -0
  28. package/lib/tx/utils.d.ts +5 -1
  29. package/lib/tx/utils.js +20 -1
  30. package/lib/userMap/userMap.js +4 -0
  31. package/package.json +1 -1
  32. package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +0 -1
  33. package/src/constants/perpMarkets.ts +20 -0
  34. package/src/driftClient.ts +197 -39
  35. package/src/events/webSocketLogProvider.ts +11 -2
  36. package/src/idl/drift.json +7 -1
  37. package/src/jupiter/jupiterClient.ts +8 -2
  38. package/src/math/funding.ts +28 -1
  39. package/src/math/oracles.ts +2 -2
  40. package/src/math/superStake.ts +60 -1
  41. package/src/priorityFee/averageOverSlotsStrategy.ts +1 -16
  42. package/src/priorityFee/maxOverSlotsStrategy.ts +1 -16
  43. package/src/priorityFee/priorityFeeSubscriber.ts +22 -26
  44. package/src/tx/baseTxSender.ts +64 -2
  45. package/src/tx/fastSingleTxSender.ts +11 -2
  46. package/src/tx/retryTxSender.ts +16 -25
  47. package/src/tx/types.ts +6 -0
  48. package/src/tx/utils.ts +32 -0
  49. package/src/userMap/userMap.ts +3 -0
  50. package/tests/amm/test.ts +275 -2
  51. package/tests/dlob/test.ts +2 -2
  52. package/tests/tx/priorityFeeStrategy.ts +2 -2
@@ -10,16 +10,20 @@ export class PriorityFeeSubscriber {
10
10
  customStrategy?: PriorityFeeStrategy;
11
11
  averageStrategy = new AverageOverSlotsStrategy();
12
12
  maxStrategy = new MaxOverSlotsStrategy();
13
+ lookbackDistance: number;
13
14
 
14
15
  intervalId?: ReturnType<typeof setTimeout>;
15
16
 
16
17
  latestPriorityFee = 0;
17
- lastStrategyResult = 0;
18
18
  lastCustomStrategyResult = 0;
19
19
  lastAvgStrategyResult = 0;
20
20
  lastMaxStrategyResult = 0;
21
21
  lastSlotSeen = 0;
22
22
 
23
+ /**
24
+ * @param props
25
+ * customStrategy : strategy to return the priority fee to use based on recent samples. defaults to AVERAGE.
26
+ */
23
27
  public constructor({
24
28
  connection,
25
29
  frequencyMs,
@@ -36,28 +40,12 @@ export class PriorityFeeSubscriber {
36
40
  this.connection = connection;
37
41
  this.frequencyMs = frequencyMs;
38
42
  this.addresses = addresses;
39
- if (slotsToCheck) {
40
- this.averageStrategy = new AverageOverSlotsStrategy(slotsToCheck);
41
- this.maxStrategy = new MaxOverSlotsStrategy(slotsToCheck);
42
- }
43
- if (customStrategy) {
43
+ if (!customStrategy) {
44
+ this.customStrategy = new AverageOverSlotsStrategy();
45
+ } else {
44
46
  this.customStrategy = customStrategy;
45
47
  }
46
- }
47
-
48
- public get avgPriorityFee(): number {
49
- return Math.floor(this.lastAvgStrategyResult);
50
- }
51
-
52
- public get maxPriorityFee(): number {
53
- return Math.floor(this.lastMaxStrategyResult);
54
- }
55
-
56
- public get customPriorityFee(): number {
57
- if (!this.customStrategy) {
58
- console.error('Custom strategy not set');
59
- }
60
- return Math.floor(this.lastCustomStrategyResult);
48
+ this.lookbackDistance = slotsToCheck;
61
49
  }
62
50
 
63
51
  public async subscribe(): Promise<void> {
@@ -75,21 +63,29 @@ export class PriorityFeeSubscriber {
75
63
  [this.addresses]
76
64
  );
77
65
 
78
- // getRecentPrioritizationFees returns results unsorted
79
66
  const results: { slot: number; prioritizationFee: number }[] =
80
67
  rpcJSONResponse?.result;
68
+
81
69
  if (!results.length) return;
82
- const descResults = results.sort((a, b) => b.slot - a.slot);
83
70
 
71
+ // # Sort and filter results based on the slot lookback setting
72
+ const descResults = results.sort((a, b) => b.slot - a.slot);
84
73
  const mostRecentResult = descResults[0];
74
+ const cutoffSlot = mostRecentResult.slot - this.lookbackDistance;
75
+
76
+ const resultsToUse = descResults.filter(
77
+ (result) => result.slot >= cutoffSlot
78
+ );
79
+
80
+ // # Handle results
85
81
  this.latestPriorityFee = mostRecentResult.prioritizationFee;
86
82
  this.lastSlotSeen = mostRecentResult.slot;
87
83
 
88
- this.lastAvgStrategyResult = this.averageStrategy.calculate(descResults);
89
- this.lastMaxStrategyResult = this.maxStrategy.calculate(descResults);
84
+ this.lastAvgStrategyResult = this.averageStrategy.calculate(resultsToUse);
85
+ this.lastMaxStrategyResult = this.maxStrategy.calculate(resultsToUse);
90
86
  if (this.customStrategy) {
91
87
  this.lastCustomStrategyResult =
92
- this.customStrategy.calculate(descResults);
88
+ this.customStrategy.calculate(resultsToUse);
93
89
  }
94
90
  }
95
91
 
@@ -1,4 +1,4 @@
1
- import { TxSender, TxSigAndSlot } from './types';
1
+ import { ConfirmationStrategy, TxSender, TxSigAndSlot } from './types';
2
2
  import {
3
3
  Commitment,
4
4
  ConfirmOptions,
@@ -28,6 +28,7 @@ export abstract class BaseTxSender implements TxSender {
28
28
  timeout: number;
29
29
  additionalConnections: Connection[];
30
30
  timeoutCount = 0;
31
+ confirmationStrategy: ConfirmationStrategy;
31
32
 
32
33
  public constructor({
33
34
  connection,
@@ -35,18 +36,21 @@ export abstract class BaseTxSender implements TxSender {
35
36
  opts = AnchorProvider.defaultOptions(),
36
37
  timeout = DEFAULT_TIMEOUT,
37
38
  additionalConnections = new Array<Connection>(),
39
+ confirmationStrategy = ConfirmationStrategy.Combo,
38
40
  }: {
39
41
  connection: Connection;
40
42
  wallet: IWallet;
41
43
  opts?: ConfirmOptions;
42
44
  timeout?: number;
43
45
  additionalConnections?;
46
+ confirmationStrategy?: ConfirmationStrategy;
44
47
  }) {
45
48
  this.connection = connection;
46
49
  this.wallet = wallet;
47
50
  this.opts = opts;
48
51
  this.timeout = timeout;
49
52
  this.additionalConnections = additionalConnections;
53
+ this.confirmationStrategy = confirmationStrategy;
50
54
  }
51
55
 
52
56
  async send(
@@ -156,7 +160,7 @@ export abstract class BaseTxSender implements TxSender {
156
160
  throw new Error('Must be implemented by subclass');
157
161
  }
158
162
 
159
- async confirmTransaction(
163
+ async confirmTransactionWebSocket(
160
164
  signature: TransactionSignature,
161
165
  commitment?: Commitment
162
166
  ): Promise<RpcResponseAndContext<SignatureResult>> {
@@ -210,6 +214,22 @@ export abstract class BaseTxSender implements TxSender {
210
214
  }
211
215
 
212
216
  if (response === null) {
217
+ if (this.confirmationStrategy === ConfirmationStrategy.Combo) {
218
+ try {
219
+ const rpcResponse = await this.connection.getSignatureStatus(
220
+ signature
221
+ );
222
+ if (rpcResponse?.value?.confirmationStatus) {
223
+ response = {
224
+ context: rpcResponse.context,
225
+ value: { err: rpcResponse.value.err },
226
+ };
227
+ return response;
228
+ }
229
+ } catch (error) {
230
+ // Ignore error to pass through to timeout error
231
+ }
232
+ }
213
233
  this.timeoutCount += 1;
214
234
  const duration = (Date.now() - start) / 1000;
215
235
  throw new Error(
@@ -222,6 +242,48 @@ export abstract class BaseTxSender implements TxSender {
222
242
  return response;
223
243
  }
224
244
 
245
+ async confirmTransactionPolling(
246
+ signature: TransactionSignature,
247
+ commitment: Commitment = 'finalized'
248
+ ): Promise<RpcResponseAndContext<SignatureResult> | undefined> {
249
+ let totalTime = 0;
250
+ let backoffTime = 400; // approx block time
251
+
252
+ while (totalTime < this.timeout) {
253
+ await new Promise((resolve) => setTimeout(resolve, backoffTime));
254
+
255
+ const response = await this.connection.getSignatureStatus(signature);
256
+ const result = response && response.value?.[0];
257
+
258
+ if (result && result.confirmationStatus === commitment) {
259
+ return { context: result.context, value: { err: null } };
260
+ }
261
+
262
+ totalTime += backoffTime;
263
+ backoffTime = Math.min(backoffTime * 2, 5000);
264
+ }
265
+
266
+ // Transaction not confirmed within 30 seconds
267
+ this.timeoutCount += 1;
268
+ throw new Error(
269
+ `Transaction was not confirmed in 30 seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`
270
+ );
271
+ }
272
+
273
+ async confirmTransaction(
274
+ signature: TransactionSignature,
275
+ commitment?: Commitment
276
+ ): Promise<RpcResponseAndContext<SignatureResult>> {
277
+ if (
278
+ this.confirmationStrategy === ConfirmationStrategy.WebSocket ||
279
+ this.confirmationStrategy === ConfirmationStrategy.Combo
280
+ ) {
281
+ return await this.confirmTransactionWebSocket(signature, commitment);
282
+ } else if (this.confirmationStrategy === ConfirmationStrategy.Polling) {
283
+ return await this.confirmTransactionPolling(signature, commitment);
284
+ }
285
+ }
286
+
225
287
  getTimestamp(): number {
226
288
  return new Date().getTime();
227
289
  }
@@ -1,4 +1,4 @@
1
- import { TxSigAndSlot } from './types';
1
+ import { ConfirmationStrategy, TxSigAndSlot } from './types';
2
2
  import {
3
3
  ConfirmOptions,
4
4
  Signer,
@@ -39,6 +39,7 @@ export class FastSingleTxSender extends BaseTxSender {
39
39
  additionalConnections = new Array<Connection>(),
40
40
  skipConfirmation = false,
41
41
  blockhashCommitment = 'finalized',
42
+ confirmationStrategy = ConfirmationStrategy.Combo,
42
43
  }: {
43
44
  connection: Connection;
44
45
  wallet: IWallet;
@@ -48,8 +49,16 @@ export class FastSingleTxSender extends BaseTxSender {
48
49
  additionalConnections?;
49
50
  skipConfirmation?: boolean;
50
51
  blockhashCommitment?: Commitment;
52
+ confirmationStrategy?: ConfirmationStrategy;
51
53
  }) {
52
- super({ connection, wallet, opts, timeout, additionalConnections });
54
+ super({
55
+ connection,
56
+ wallet,
57
+ opts,
58
+ timeout,
59
+ additionalConnections,
60
+ confirmationStrategy,
61
+ });
53
62
  this.connection = connection;
54
63
  this.wallet = wallet;
55
64
  this.opts = opts;
@@ -1,9 +1,5 @@
1
- import { TxSigAndSlot } from './types';
2
- import {
3
- ConfirmOptions,
4
- TransactionSignature,
5
- Connection,
6
- } from '@solana/web3.js';
1
+ import { ConfirmationStrategy, TxSigAndSlot } from './types';
2
+ import { ConfirmOptions, Connection } from '@solana/web3.js';
7
3
  import { AnchorProvider } from '@coral-xyz/anchor';
8
4
  import { IWallet } from '../types';
9
5
  import { BaseTxSender } from './baseTxSender';
@@ -31,6 +27,7 @@ export class RetryTxSender extends BaseTxSender {
31
27
  timeout = DEFAULT_TIMEOUT,
32
28
  retrySleep = DEFAULT_RETRY,
33
29
  additionalConnections = new Array<Connection>(),
30
+ confirmationStrategy = ConfirmationStrategy.Combo,
34
31
  }: {
35
32
  connection: Connection;
36
33
  wallet: IWallet;
@@ -38,8 +35,16 @@ export class RetryTxSender extends BaseTxSender {
38
35
  timeout?: number;
39
36
  retrySleep?: number;
40
37
  additionalConnections?;
38
+ confirmationStrategy?: ConfirmationStrategy;
41
39
  }) {
42
- super({ connection, wallet, opts, timeout, additionalConnections });
40
+ super({
41
+ connection,
42
+ wallet,
43
+ opts,
44
+ timeout,
45
+ additionalConnections,
46
+ confirmationStrategy,
47
+ });
43
48
  this.connection = connection;
44
49
  this.wallet = wallet;
45
50
  this.opts = opts;
@@ -61,14 +66,8 @@ export class RetryTxSender extends BaseTxSender {
61
66
  ): Promise<TxSigAndSlot> {
62
67
  const startTime = this.getTimestamp();
63
68
 
64
- let txid: TransactionSignature;
65
- try {
66
- txid = await this.connection.sendRawTransaction(rawTransaction, opts);
67
- this.sendToAdditionalConnections(rawTransaction, opts);
68
- } catch (e) {
69
- console.error(e);
70
- throw e;
71
- }
69
+ const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
70
+ this.sendToAdditionalConnections(rawTransaction, opts);
72
71
 
73
72
  let done = false;
74
73
  const resolveReference: ResolveReference = {
@@ -96,16 +95,8 @@ export class RetryTxSender extends BaseTxSender {
96
95
  }
97
96
  })();
98
97
 
99
- let slot: number;
100
- try {
101
- const result = await this.confirmTransaction(txid, opts.commitment);
102
- slot = result.context.slot;
103
- } catch (e) {
104
- console.error(e);
105
- throw e;
106
- } finally {
107
- stopWaiting();
108
- }
98
+ const result = await this.confirmTransaction(txid, opts.commitment);
99
+ const slot = result.context.slot;
109
100
 
110
101
  return { txSig: txid, slot };
111
102
  }
package/src/tx/types.ts CHANGED
@@ -9,6 +9,12 @@ import {
9
9
  } from '@solana/web3.js';
10
10
  import { IWallet } from '../types';
11
11
 
12
+ export enum ConfirmationStrategy {
13
+ WebSocket = 'websocket',
14
+ Polling = 'polling',
15
+ Combo = 'combo',
16
+ }
17
+
12
18
  export type TxSigAndSlot = {
13
19
  txSig: TransactionSignature;
14
20
  slot: number;
package/src/tx/utils.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { Wallet } from '@coral-xyz/anchor';
1
2
  import {
2
3
  Transaction,
3
4
  TransactionInstruction,
4
5
  ComputeBudgetProgram,
6
+ VersionedTransaction,
5
7
  } from '@solana/web3.js';
6
8
 
7
9
  const COMPUTE_UNITS_DEFAULT = 200_000;
@@ -30,3 +32,33 @@ export function wrapInTx(
30
32
 
31
33
  return tx.add(instruction);
32
34
  }
35
+
36
+ /* Helper function for signing multiple transactions where some may be undefined and mapping the output */
37
+ export async function getSignedTransactionMap(
38
+ wallet: Wallet,
39
+ txsToSign: (Transaction | VersionedTransaction | undefined)[],
40
+ keys: string[]
41
+ ): Promise<{ [key: string]: Transaction | VersionedTransaction | undefined }> {
42
+ const signedTxMap: {
43
+ [key: string]: Transaction | VersionedTransaction | undefined;
44
+ } = {};
45
+
46
+ const keysWithTx = [];
47
+ txsToSign.forEach((tx, index) => {
48
+ if (tx == undefined) {
49
+ signedTxMap[keys[index]] = undefined;
50
+ } else {
51
+ keysWithTx.push(keys[index]);
52
+ }
53
+ });
54
+
55
+ const signedTxs = await wallet.signAllTransactions(
56
+ txsToSign.filter((tx) => tx !== undefined)
57
+ );
58
+
59
+ signedTxs.forEach((signedTx, index) => {
60
+ signedTxMap[keysWithTx[index]] = signedTx;
61
+ });
62
+
63
+ return signedTxMap;
64
+ }
@@ -337,6 +337,9 @@ export class UserMap implements UserMapInterface {
337
337
  const userAccount = this.decode('User', buffer);
338
338
  await this.addPubkey(new PublicKey(key), userAccount);
339
339
  this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
340
+ } else {
341
+ const userAccount = this.decode('User', buffer);
342
+ this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
340
343
  }
341
344
  // give event loop a chance to breathe
342
345
  await new Promise((resolve) => setTimeout(resolve, 0));
package/tests/amm/test.ts CHANGED
@@ -28,6 +28,9 @@ import {
28
28
  squareRootBN,
29
29
  calculateReferencePriceOffset,
30
30
  calculateInventoryLiquidityRatio,
31
+ ContractTier,
32
+ isOracleValid,
33
+ OracleGuardRails,
31
34
  } from '../../src';
32
35
  import { mockPerpMarkets } from '../dlob/helpers';
33
36
 
@@ -875,11 +878,12 @@ describe('AMM Tests', () => {
875
878
  const mockMarket1 = myMockPerpMarkets[0];
876
879
  const mockAmm = mockMarket1.amm;
877
880
  const now = new BN(new Date().getTime() / 1000); //todo
881
+ const slot = 999999999;
878
882
 
879
883
  const oraclePriceData = {
880
884
  price: new BN(13.553 * PRICE_PRECISION.toNumber()),
881
- slot: new BN(68 + 1),
882
- confidence: new BN(1),
885
+ slot: new BN(slot),
886
+ confidence: new BN(1000),
883
887
  hasSufficientNumberOfDataPoints: true,
884
888
  };
885
889
  mockAmm.oracleStd = new BN(0.18 * PRICE_PRECISION.toNumber());
@@ -901,6 +905,127 @@ describe('AMM Tests', () => {
901
905
  const liveOracleStd = calculateLiveOracleStd(mockAmm, oraclePriceData, now);
902
906
  console.log('liveOracleStd:', liveOracleStd.toNumber());
903
907
  assert(liveOracleStd.eq(new BN(192962)));
908
+
909
+ const oracleGuardRails: OracleGuardRails = {
910
+ priceDivergence: {
911
+ markOraclePercentDivergence: PERCENTAGE_PRECISION.divn(10),
912
+ oracleTwap5MinPercentDivergence: PERCENTAGE_PRECISION.divn(10),
913
+ },
914
+ validity: {
915
+ slotsBeforeStaleForAmm: new BN(10),
916
+ slotsBeforeStaleForMargin: new BN(60),
917
+ confidenceIntervalMaxSize: new BN(20000),
918
+ tooVolatileRatio: new BN(5),
919
+ },
920
+ };
921
+
922
+ // good oracle
923
+ assert(isOracleValid(mockAmm, oraclePriceData, oracleGuardRails, slot + 5));
924
+
925
+ // conf too high
926
+ assert(
927
+ !isOracleValid(
928
+ mockAmm,
929
+ {
930
+ price: new BN(13.553 * PRICE_PRECISION.toNumber()),
931
+ slot: new BN(slot),
932
+ confidence: new BN(13.553 * PRICE_PRECISION.toNumber() * 0.021),
933
+ hasSufficientNumberOfDataPoints: true,
934
+ },
935
+ oracleGuardRails,
936
+ slot
937
+ )
938
+ );
939
+
940
+ // not hasSufficientNumberOfDataPoints
941
+ assert(
942
+ !isOracleValid(
943
+ mockAmm,
944
+ {
945
+ price: new BN(13.553 * PRICE_PRECISION.toNumber()),
946
+ slot: new BN(slot),
947
+ confidence: new BN(1),
948
+ hasSufficientNumberOfDataPoints: false,
949
+ },
950
+ oracleGuardRails,
951
+ slot
952
+ )
953
+ );
954
+
955
+ // negative oracle price
956
+ assert(
957
+ !isOracleValid(
958
+ mockAmm,
959
+ {
960
+ price: new BN(-1 * PRICE_PRECISION.toNumber()),
961
+ slot: new BN(slot),
962
+ confidence: new BN(1),
963
+ hasSufficientNumberOfDataPoints: true,
964
+ },
965
+ oracleGuardRails,
966
+ slot
967
+ )
968
+ );
969
+
970
+ // too delayed for amm
971
+ assert(
972
+ !isOracleValid(
973
+ mockAmm,
974
+ {
975
+ price: new BN(13.553 * PRICE_PRECISION.toNumber()),
976
+ slot: new BN(slot),
977
+ confidence: new BN(1),
978
+ hasSufficientNumberOfDataPoints: true,
979
+ },
980
+ oracleGuardRails,
981
+ slot + 100
982
+ )
983
+ );
984
+
985
+ // im passing stale slot (should not call oracle invalid)
986
+ assert(
987
+ isOracleValid(
988
+ mockAmm,
989
+ {
990
+ price: new BN(13.553 * PRICE_PRECISION.toNumber()),
991
+ slot: new BN(slot + 100),
992
+ confidence: new BN(1),
993
+ hasSufficientNumberOfDataPoints: true,
994
+ },
995
+ oracleGuardRails,
996
+ slot
997
+ )
998
+ );
999
+
1000
+ // too volatile (more than 5x higher)
1001
+ assert(
1002
+ !isOracleValid(
1003
+ mockAmm,
1004
+ {
1005
+ price: new BN(113.553 * PRICE_PRECISION.toNumber()),
1006
+ slot: new BN(slot + 5),
1007
+ confidence: new BN(1),
1008
+ hasSufficientNumberOfDataPoints: true,
1009
+ },
1010
+ oracleGuardRails,
1011
+ slot
1012
+ )
1013
+ );
1014
+
1015
+ // too volatile (more than 1/5 lower)
1016
+ assert(
1017
+ !isOracleValid(
1018
+ mockAmm,
1019
+ {
1020
+ price: new BN(0.553 * PRICE_PRECISION.toNumber()),
1021
+ slot: new BN(slot + 5),
1022
+ confidence: new BN(1),
1023
+ hasSufficientNumberOfDataPoints: true,
1024
+ },
1025
+ oracleGuardRails,
1026
+ slot
1027
+ )
1028
+ );
904
1029
  });
905
1030
 
906
1031
  it('predicted funding rate mock1', async () => {
@@ -1065,6 +1190,154 @@ describe('AMM Tests', () => {
1065
1190
  assert(est2.eq(new BN('-719')));
1066
1191
  });
1067
1192
 
1193
+ it('predicted funding rate mock clamp', async () => {
1194
+ const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
1195
+ const mockMarket1 = myMockPerpMarkets[0];
1196
+
1197
+ // make it like OP
1198
+ const now = new BN(1688881915);
1199
+
1200
+ mockMarket1.amm.fundingPeriod = new BN(3600);
1201
+ mockMarket1.amm.lastFundingRateTs = new BN(1688864415);
1202
+
1203
+ const currentMarkPrice = new BN(1.2242 * PRICE_PRECISION.toNumber()); // trading at a premium
1204
+ const oraclePriceData: OraclePriceData = {
1205
+ price: new BN(1.924 * PRICE_PRECISION.toNumber()),
1206
+ slot: new BN(0),
1207
+ confidence: new BN(1),
1208
+ hasSufficientNumberOfDataPoints: true,
1209
+ };
1210
+ mockMarket1.amm.historicalOracleData.lastOraclePrice = new BN(
1211
+ 1.9535 * PRICE_PRECISION.toNumber()
1212
+ );
1213
+
1214
+ // mockMarket1.amm.pegMultiplier = new BN(1.897573 * 1e3);
1215
+
1216
+ mockMarket1.amm.lastMarkPriceTwap = new BN(
1217
+ 1.218363 * PRICE_PRECISION.toNumber()
1218
+ );
1219
+ mockMarket1.amm.lastBidPriceTwap = new BN(
1220
+ 1.218363 * PRICE_PRECISION.toNumber()
1221
+ );
1222
+ mockMarket1.amm.lastAskPriceTwap = new BN(
1223
+ 1.218364 * PRICE_PRECISION.toNumber()
1224
+ );
1225
+ mockMarket1.amm.lastMarkPriceTwapTs = new BN(1688878815);
1226
+
1227
+ mockMarket1.amm.historicalOracleData.lastOraclePriceTwap = new BN(
1228
+ 1.820964 * PRICE_PRECISION.toNumber()
1229
+ );
1230
+ mockMarket1.amm.historicalOracleData.lastOraclePriceTwapTs = new BN(
1231
+ 1688879991
1232
+ );
1233
+ mockMarket1.contractTier = ContractTier.A;
1234
+
1235
+ const [
1236
+ _markTwapLive,
1237
+ _oracleTwapLive,
1238
+ _lowerboundEst,
1239
+ _cappedAltEst,
1240
+ _interpEst,
1241
+ ] = await calculateAllEstimatedFundingRate(
1242
+ mockMarket1,
1243
+ oraclePriceData,
1244
+ currentMarkPrice,
1245
+ now
1246
+ );
1247
+
1248
+ // console.log(_markTwapLive.toString());
1249
+ // console.log(_oracleTwapLive.toString());
1250
+ // console.log(_lowerboundEst.toString());
1251
+ // console.log(_cappedAltEst.toString());
1252
+ // console.log(_interpEst.toString());
1253
+ // console.log('-----');
1254
+
1255
+ let [markTwapLive, oracleTwapLive, est1, est2] =
1256
+ await calculateLongShortFundingRateAndLiveTwaps(
1257
+ mockMarket1,
1258
+ oraclePriceData,
1259
+ currentMarkPrice,
1260
+ now
1261
+ );
1262
+
1263
+ console.log(
1264
+ 'markTwapLive:',
1265
+ mockMarket1.amm.lastMarkPriceTwap.toString(),
1266
+ '->',
1267
+ markTwapLive.toString()
1268
+ );
1269
+ console.log(
1270
+ 'oracTwapLive:',
1271
+ mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(),
1272
+ '->',
1273
+ oracleTwapLive.toString()
1274
+ );
1275
+ console.log('pred funding:', est1.toString(), est2.toString());
1276
+
1277
+ assert(markTwapLive.eq(new BN('1680634')));
1278
+ assert(oracleTwapLive.eq(new BN('1876031')));
1279
+ assert(est1.eq(est2));
1280
+ assert(est2.eq(new BN('-126261')));
1281
+
1282
+ mockMarket1.contractTier = ContractTier.C;
1283
+
1284
+ [markTwapLive, oracleTwapLive, est1, est2] =
1285
+ await calculateLongShortFundingRateAndLiveTwaps(
1286
+ mockMarket1,
1287
+ oraclePriceData,
1288
+ currentMarkPrice,
1289
+ now
1290
+ );
1291
+
1292
+ console.log(
1293
+ 'markTwapLive:',
1294
+ mockMarket1.amm.lastMarkPriceTwap.toString(),
1295
+ '->',
1296
+ markTwapLive.toString()
1297
+ );
1298
+ console.log(
1299
+ 'oracTwapLive:',
1300
+ mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(),
1301
+ '->',
1302
+ oracleTwapLive.toString()
1303
+ );
1304
+ console.log('pred funding:', est1.toString(), est2.toString());
1305
+
1306
+ assert(markTwapLive.eq(new BN('1680634')));
1307
+ assert(oracleTwapLive.eq(new BN('1876031')));
1308
+ assert(est1.eq(est2));
1309
+ assert(est2.eq(new BN('-208332')));
1310
+
1311
+ mockMarket1.contractTier = ContractTier.SPECULATIVE;
1312
+
1313
+ [markTwapLive, oracleTwapLive, est1, est2] =
1314
+ await calculateLongShortFundingRateAndLiveTwaps(
1315
+ mockMarket1,
1316
+ oraclePriceData,
1317
+ currentMarkPrice,
1318
+ now
1319
+ );
1320
+
1321
+ console.log(
1322
+ 'markTwapLive:',
1323
+ mockMarket1.amm.lastMarkPriceTwap.toString(),
1324
+ '->',
1325
+ markTwapLive.toString()
1326
+ );
1327
+ console.log(
1328
+ 'oracTwapLive:',
1329
+ mockMarket1.amm.historicalOracleData.lastOraclePriceTwap.toString(),
1330
+ '->',
1331
+ oracleTwapLive.toString()
1332
+ );
1333
+ console.log('pred funding:', est1.toString(), est2.toString());
1334
+
1335
+ assert(markTwapLive.eq(new BN('1680634')));
1336
+ assert(oracleTwapLive.eq(new BN('1876031')));
1337
+ assert(est1.eq(est2));
1338
+ assert(est2.eq(new BN('-416666')));
1339
+ });
1340
+
1068
1341
  it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => {
1069
1342
  const myMockPerpMarkets = _.cloneDeep(mockPerpMarkets);
1070
1343
 
@@ -76,7 +76,7 @@ function insertOrderToDLOB(
76
76
  auctionEndPrice,
77
77
  maxTs,
78
78
  },
79
- userAccount,
79
+ userAccount.toString(),
80
80
  slot.toNumber()
81
81
  );
82
82
  }
@@ -127,7 +127,7 @@ function insertTriggerOrderToDLOB(
127
127
  auctionEndPrice,
128
128
  maxTs,
129
129
  },
130
- userAccount,
130
+ userAccount.toString(),
131
131
  slot.toNumber()
132
132
  );
133
133
  }