@drift-labs/sdk 0.1.22 → 0.1.23-master.3

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 (44) hide show
  1. package/lib/accounts/bulkAccountLoader.js +2 -2
  2. package/lib/accounts/bulkUserSubscription.js +39 -1
  3. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +3 -3
  4. package/lib/accounts/pollingTokenAccountSubscriber.js +2 -2
  5. package/lib/accounts/pollingUserAccountSubscriber.d.ts +2 -2
  6. package/lib/accounts/pollingUserAccountSubscriber.js +39 -18
  7. package/lib/accounts/types.d.ts +5 -0
  8. package/lib/accounts/webSocketAccountSubscriber.js +2 -2
  9. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +1 -1
  10. package/lib/accounts/webSocketUserAccountSubscriber.js +2 -2
  11. package/lib/admin.js +7 -7
  12. package/lib/clearingHouse.js +21 -22
  13. package/lib/clearingHouseUser.js +21 -21
  14. package/lib/examples/makeTradeExample.js +6 -6
  15. package/lib/idl/clearing_house.json +68 -0
  16. package/lib/index.d.ts +2 -0
  17. package/lib/index.js +2 -0
  18. package/lib/math/amm.js +12 -12
  19. package/lib/math/conversion.js +1 -1
  20. package/lib/math/funding.js +1 -1
  21. package/lib/math/market.js +2 -2
  22. package/lib/math/orders.d.ts +1 -0
  23. package/lib/math/orders.js +26 -4
  24. package/lib/math/position.js +4 -3
  25. package/lib/math/trade.js +21 -17
  26. package/lib/orders.d.ts +3 -1
  27. package/lib/orders.js +47 -19
  28. package/lib/pythClient.js +1 -1
  29. package/lib/tx/retryTxSender.d.ts +19 -0
  30. package/lib/tx/retryTxSender.js +153 -0
  31. package/lib/tx/types.d.ts +2 -0
  32. package/package.json +1 -1
  33. package/src/accounts/bulkUserSubscription.ts +51 -1
  34. package/src/accounts/pollingUserAccountSubscriber.ts +49 -30
  35. package/src/accounts/types.ts +6 -0
  36. package/src/clearingHouse.ts +3 -3
  37. package/src/idl/clearing_house.json +68 -0
  38. package/src/index.ts +2 -0
  39. package/src/math/orders.ts +33 -0
  40. package/src/math/position.ts +3 -2
  41. package/src/math/trade.ts +8 -3
  42. package/src/orders.ts +56 -3
  43. package/src/tx/retryTxSender.ts +196 -0
  44. package/src/tx/types.ts +3 -0
@@ -42,3 +42,36 @@ export function isOrderRiskIncreasing(
42
42
 
43
43
  return false;
44
44
  }
45
+
46
+ export function isOrderRiskIncreasingInSameDirection(
47
+ user: ClearingHouseUser,
48
+ order: Order
49
+ ): boolean {
50
+ if (isVariant(order.status, 'init')) {
51
+ return false;
52
+ }
53
+
54
+ const position =
55
+ user.getUserPosition(order.marketIndex) ||
56
+ user.getEmptyPosition(order.marketIndex);
57
+
58
+ // if no position exists, it's risk increasing
59
+ if (position.baseAssetAmount.eq(ZERO)) {
60
+ return true;
61
+ }
62
+
63
+ // if position is long and order is long
64
+ if (position.baseAssetAmount.gt(ZERO) && isVariant(order.direction, 'long')) {
65
+ return true;
66
+ }
67
+
68
+ // if position is short and order is short
69
+ if (
70
+ position.baseAssetAmount.lt(ZERO) &&
71
+ isVariant(order.direction, 'short')
72
+ ) {
73
+ return true;
74
+ }
75
+
76
+ return false;
77
+ }
@@ -47,7 +47,8 @@ export function calculateBaseAssetValue(
47
47
  return newQuoteAssetReserve
48
48
  .sub(market.amm.quoteAssetReserve)
49
49
  .mul(market.amm.pegMultiplier)
50
- .div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO);
50
+ .div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
51
+ .add(ONE);
51
52
  }
52
53
  }
53
54
 
@@ -74,7 +75,7 @@ export function calculatePositionPNL(
74
75
  if (marketPosition.baseAssetAmount.gt(ZERO)) {
75
76
  pnl = baseAssetValue.sub(marketPosition.quoteAssetAmount);
76
77
  } else {
77
- pnl = marketPosition.quoteAssetAmount.sub(baseAssetValue).sub(ONE);
78
+ pnl = marketPosition.quoteAssetAmount.sub(baseAssetValue);
78
79
  }
79
80
 
80
81
  if (withFunding) {
package/src/math/trade.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Market, PositionDirection } from '../types';
1
+ import { isVariant, Market, PositionDirection } from '../types';
2
2
  import { BN } from '@project-serum/anchor';
3
3
  import { assert } from '../assert/assert';
4
4
  import {
@@ -6,6 +6,7 @@ import {
6
6
  PEG_PRECISION,
7
7
  AMM_TO_QUOTE_PRECISION_RATIO,
8
8
  ZERO,
9
+ ONE,
9
10
  } from '../constants/numericConstants';
10
11
  import { calculateMarkPrice } from './market';
11
12
  import {
@@ -114,16 +115,20 @@ export function calculateTradeAcquiredAmounts(
114
115
  return [ZERO, ZERO];
115
116
  }
116
117
 
118
+ const swapDirection = getSwapDirection(inputAssetType, direction);
117
119
  const [newQuoteAssetReserve, newBaseAssetReserve] =
118
120
  calculateAmmReservesAfterSwap(
119
121
  market.amm,
120
122
  inputAssetType,
121
123
  amount,
122
- getSwapDirection(inputAssetType, direction)
124
+ swapDirection
123
125
  );
124
126
 
125
127
  const acquiredBase = market.amm.baseAssetReserve.sub(newBaseAssetReserve);
126
- const acquiredQuote = market.amm.quoteAssetReserve.sub(newQuoteAssetReserve);
128
+ let acquiredQuote = market.amm.quoteAssetReserve.sub(newQuoteAssetReserve);
129
+ if (inputAssetType === 'base' && isVariant(swapDirection, 'remove')) {
130
+ acquiredQuote = acquiredQuote.sub(ONE);
131
+ }
127
132
 
128
133
  return [acquiredBase, acquiredQuote];
129
134
  }
package/src/orders.ts CHANGED
@@ -3,16 +3,25 @@ import {
3
3
  Market,
4
4
  Order,
5
5
  PositionDirection,
6
+ SwapDirection,
6
7
  UserAccount,
7
8
  UserPosition,
8
9
  } from './types';
9
- import { BN } from '.';
10
+ import {
11
+ BN,
12
+ calculateAmmReservesAfterSwap,
13
+ calculateBaseAssetValue,
14
+ ClearingHouseUser,
15
+ isOrderRiskIncreasingInSameDirection,
16
+ TEN_THOUSAND,
17
+ } from '.';
10
18
  import {
11
19
  calculateMarkPrice,
12
20
  calculateNewMarketAfterTrade,
13
21
  } from './math/market';
14
22
  import {
15
23
  AMM_TO_QUOTE_PRECISION_RATIO,
24
+ TWO,
16
25
  PEG_PRECISION,
17
26
  ZERO,
18
27
  } from './constants/numericConstants';
@@ -32,7 +41,10 @@ export function calculateNewStateAfterOrder(
32
41
  return null;
33
42
  }
34
43
 
35
- const baseAssetAmountToTrade = calculateAmountToTrade(market, order);
44
+ const baseAssetAmountToTrade = calculateBaseAssetAmountMarketCanExecute(
45
+ market,
46
+ order
47
+ );
36
48
  if (baseAssetAmountToTrade.lt(market.amm.minimumBaseAssetTradeSize)) {
37
49
  return null;
38
50
  }
@@ -157,7 +169,10 @@ function calculateAmountSwapped(
157
169
  };
158
170
  }
159
171
 
160
- function calculateAmountToTrade(market: Market, order: Order): BN {
172
+ export function calculateBaseAssetAmountMarketCanExecute(
173
+ market: Market,
174
+ order: Order
175
+ ): BN {
161
176
  if (isVariant(order.orderType, 'limit')) {
162
177
  return calculateAmountToTradeForLimit(market, order);
163
178
  } else if (isVariant(order.orderType, 'triggerLimit')) {
@@ -234,3 +249,41 @@ function isTriggerConditionSatisfied(market: Market, order: Order): boolean {
234
249
  return markPrice.lt(order.triggerPrice);
235
250
  }
236
251
  }
252
+
253
+ export function calculateBaseAssetAmountUserCanExecute(
254
+ market: Market,
255
+ order: Order,
256
+ user: ClearingHouseUser
257
+ ): BN {
258
+ const maxLeverage = user.getMaxLeverage('Initial');
259
+ const freeCollateral = user.getFreeCollateral();
260
+ let quoteAssetAmount: BN;
261
+ if (isOrderRiskIncreasingInSameDirection(user, order)) {
262
+ quoteAssetAmount = freeCollateral.mul(maxLeverage).div(TEN_THOUSAND);
263
+ } else {
264
+ const position =
265
+ user.getUserPosition(order.marketIndex) ||
266
+ user.getEmptyPosition(order.marketIndex);
267
+ const positionValue = calculateBaseAssetValue(market, position);
268
+ quoteAssetAmount = freeCollateral
269
+ .mul(maxLeverage)
270
+ .div(TEN_THOUSAND)
271
+ .add(positionValue.mul(TWO));
272
+ }
273
+
274
+ if (quoteAssetAmount.lte(ZERO)) {
275
+ return ZERO;
276
+ }
277
+
278
+ const baseAssetReservesBefore = market.amm.baseAssetReserve;
279
+ const [_, baseAssetReservesAfter] = calculateAmmReservesAfterSwap(
280
+ market.amm,
281
+ 'quote',
282
+ quoteAssetAmount,
283
+ isVariant(order.direction, 'long')
284
+ ? SwapDirection.ADD
285
+ : SwapDirection.REMOVE
286
+ );
287
+
288
+ return baseAssetReservesBefore.sub(baseAssetReservesAfter).abs();
289
+ }
@@ -0,0 +1,196 @@
1
+ import { TxSender } from './types';
2
+ import {
3
+ Commitment,
4
+ ConfirmOptions,
5
+ Context,
6
+ RpcResponseAndContext,
7
+ Signer,
8
+ SignatureResult,
9
+ Transaction,
10
+ TransactionSignature,
11
+ } from '@solana/web3.js';
12
+ import { Provider } from '@project-serum/anchor';
13
+ import assert from 'assert';
14
+ import bs58 from 'bs58';
15
+
16
+ const DEFAULT_TIMEOUT = 35000;
17
+ const DEFAULT_RETRY = 8000;
18
+
19
+ type ResolveReference = {
20
+ resolve?: () => void;
21
+ };
22
+
23
+ export class RetryTxSender implements TxSender {
24
+ provider: Provider;
25
+ timeout: number;
26
+ retrySleep: number;
27
+
28
+ public constructor(
29
+ provider: Provider,
30
+ timeout?: number,
31
+ retrySleep?: number
32
+ ) {
33
+ this.provider = provider;
34
+ this.timeout = timeout ?? DEFAULT_TIMEOUT;
35
+ this.retrySleep = retrySleep ?? DEFAULT_RETRY;
36
+ }
37
+
38
+ async send(
39
+ tx: Transaction,
40
+ additionalSigners?: Array<Signer>,
41
+ opts?: ConfirmOptions
42
+ ): Promise<TransactionSignature> {
43
+ if (additionalSigners === undefined) {
44
+ additionalSigners = [];
45
+ }
46
+ if (opts === undefined) {
47
+ opts = this.provider.opts;
48
+ }
49
+
50
+ await this.prepareTx(tx, additionalSigners, opts);
51
+
52
+ const rawTransaction = tx.serialize();
53
+ const startTime = this.getTimestamp();
54
+
55
+ const txid: TransactionSignature =
56
+ await this.provider.connection.sendRawTransaction(rawTransaction, opts);
57
+
58
+ let done = false;
59
+ const resolveReference: ResolveReference = {
60
+ resolve: undefined,
61
+ };
62
+ const stopWaiting = () => {
63
+ done = true;
64
+ if (resolveReference.resolve) {
65
+ resolveReference.resolve();
66
+ }
67
+ };
68
+
69
+ (async () => {
70
+ while (!done && this.getTimestamp() - startTime < this.timeout) {
71
+ await this.sleep(resolveReference);
72
+ if (!done) {
73
+ this.provider.connection
74
+ .sendRawTransaction(rawTransaction, opts)
75
+ .catch((e) => {
76
+ console.error(e);
77
+ stopWaiting();
78
+ });
79
+ }
80
+ }
81
+ })();
82
+
83
+ try {
84
+ await this.confirmTransaction(txid, opts.commitment);
85
+ } catch (e) {
86
+ console.error(e);
87
+ throw e;
88
+ } finally {
89
+ stopWaiting();
90
+ }
91
+
92
+ return txid;
93
+ }
94
+
95
+ async prepareTx(
96
+ tx: Transaction,
97
+ additionalSigners: Array<Signer>,
98
+ opts: ConfirmOptions
99
+ ): Promise<Transaction> {
100
+ tx.feePayer = this.provider.wallet.publicKey;
101
+ tx.recentBlockhash = (
102
+ await this.provider.connection.getRecentBlockhash(
103
+ opts.preflightCommitment
104
+ )
105
+ ).blockhash;
106
+
107
+ await this.provider.wallet.signTransaction(tx);
108
+ additionalSigners
109
+ .filter((s): s is Signer => s !== undefined)
110
+ .forEach((kp) => {
111
+ tx.partialSign(kp);
112
+ });
113
+
114
+ return tx;
115
+ }
116
+
117
+ async confirmTransaction(
118
+ signature: TransactionSignature,
119
+ commitment?: Commitment
120
+ ): Promise<RpcResponseAndContext<SignatureResult>> {
121
+ let decodedSignature;
122
+ try {
123
+ decodedSignature = bs58.decode(signature);
124
+ } catch (err) {
125
+ throw new Error('signature must be base58 encoded: ' + signature);
126
+ }
127
+
128
+ assert(decodedSignature.length === 64, 'signature has invalid length');
129
+
130
+ const start = Date.now();
131
+ const subscriptionCommitment = commitment || this.provider.opts.commitment;
132
+
133
+ let subscriptionId;
134
+ let response: RpcResponseAndContext<SignatureResult> | null = null;
135
+ const confirmPromise = new Promise((resolve, reject) => {
136
+ try {
137
+ subscriptionId = this.provider.connection.onSignature(
138
+ signature,
139
+ (result: SignatureResult, context: Context) => {
140
+ subscriptionId = undefined;
141
+ response = {
142
+ context,
143
+ value: result,
144
+ };
145
+ resolve(null);
146
+ },
147
+ subscriptionCommitment
148
+ );
149
+ } catch (err) {
150
+ reject(err);
151
+ }
152
+ });
153
+
154
+ try {
155
+ await this.promiseTimeout(confirmPromise, this.timeout);
156
+ } finally {
157
+ if (subscriptionId) {
158
+ this.provider.connection.removeSignatureListener(subscriptionId);
159
+ }
160
+ }
161
+
162
+ if (response === null) {
163
+ const duration = (Date.now() - start) / 1000;
164
+ throw new Error(
165
+ `Transaction was not confirmed in ${duration.toFixed(
166
+ 2
167
+ )} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`
168
+ );
169
+ }
170
+
171
+ return response;
172
+ }
173
+
174
+ getTimestamp(): number {
175
+ return new Date().getTime();
176
+ }
177
+
178
+ async sleep(reference: ResolveReference): Promise<void> {
179
+ return new Promise((resolve) => {
180
+ reference.resolve = resolve;
181
+ setTimeout(resolve, this.retrySleep);
182
+ });
183
+ }
184
+
185
+ promiseTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null> {
186
+ let timeoutId: ReturnType<typeof setTimeout>;
187
+ const timeoutPromise: Promise<null> = new Promise((resolve) => {
188
+ timeoutId = setTimeout(() => resolve(null), timeoutMs);
189
+ });
190
+
191
+ return Promise.race([promise, timeoutPromise]).then((result: T | null) => {
192
+ clearTimeout(timeoutId);
193
+ return result;
194
+ });
195
+ }
196
+ }
package/src/tx/types.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Provider } from '@project-serum/anchor';
1
2
  import {
2
3
  ConfirmOptions,
3
4
  Signer,
@@ -6,6 +7,8 @@ import {
6
7
  } from '@solana/web3.js';
7
8
 
8
9
  export interface TxSender {
10
+ provider: Provider;
11
+
9
12
  send(
10
13
  tx: Transaction,
11
14
  additionalSigners?: Array<Signer>,