@drift-labs/sdk 2.12.0-beta.0 → 2.12.0-beta.1

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.
package/README.md CHANGED
@@ -11,14 +11,6 @@
11
11
  </p>
12
12
  </div>
13
13
 
14
- # Drift Protocol v2
15
-
16
- This repository provides open source access to Drift's Typescript SDK, Solana Programs, and more.
17
-
18
- # SDK Guide
19
-
20
- The technical documentation for the SDK can be found [here](https://drift-labs.github.io/protocol-v2/sdk/), and you can visit Drift's general purpose documentation [here](https://docs.drift.trade/sdk-documentation).
21
-
22
14
  ## Installation
23
15
 
24
16
  ```
@@ -84,7 +76,7 @@ convertToNumber(new BN(10500), new BN(1000)); // = 10.5
84
76
  ### Setting up an account and making a trade
85
77
 
86
78
  ```typescript
87
- import { BN, Provider } from '@project-serum/anchor';
79
+ import { AnchorProvider, BN } from '@project-serum/anchor';
88
80
  import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
89
81
  import { Connection, Keypair, PublicKey } from '@solana/web3.js';
90
82
  import {
@@ -92,14 +84,18 @@ import {
92
84
  DriftClient,
93
85
  User,
94
86
  initialize,
95
- Markets,
96
87
  PositionDirection,
97
88
  convertToNumber,
98
89
  calculateTradeSlippage,
99
90
  PRICE_PRECISION,
100
91
  QUOTE_PRECISION,
101
92
  Wallet,
102
- } from '@drift-labs/sdk';
93
+ PerpMarkets,
94
+ BASE_PRECISION,
95
+ getMarketOrderParams,
96
+ BulkAccountLoader,
97
+ getMarketsAndOraclesForSubscription
98
+ } from '../sdk';
103
99
 
104
100
  export const getTokenAddress = (
105
101
  mintAddress: string,
@@ -114,8 +110,9 @@ export const getTokenAddress = (
114
110
  };
115
111
 
116
112
  const main = async () => {
113
+ const env = 'devnet';
117
114
  // Initialize Drift SDK
118
- const sdkConfig = initialize({ env: 'devnet' });
115
+ const sdkConfig = initialize({ env });
119
116
 
120
117
  // Set up the Wallet and Provider
121
118
  const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
@@ -129,7 +126,11 @@ const main = async () => {
129
126
  const connection = new Connection(rpcAddress);
130
127
 
131
128
  // Set up the Provider
132
- const provider = new Provider(connection, wallet, Provider.defaultOptions());
129
+ const provider = new AnchorProvider(
130
+ connection,
131
+ wallet,
132
+ AnchorProvider.defaultOptions()
133
+ );
133
134
 
134
135
  // Check SOL Balance
135
136
  const lamportsBalance = await connection.getBalance(wallet.publicKey);
@@ -142,18 +143,35 @@ const main = async () => {
142
143
  );
143
144
 
144
145
  // Set up the Drift Client
145
- const driftClientPublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
146
- const driftClient = DriftClient.from(
146
+ const driftPublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
147
+ const bulkAccountLoader = new BulkAccountLoader(
147
148
  connection,
148
- provider.wallet,
149
- driftClientPublicKey
149
+ 'confirmed',
150
+ 1000
150
151
  );
152
+ const driftClient = new DriftClient({
153
+ connection,
154
+ wallet: provider.wallet,
155
+ programID: driftPublicKey,
156
+ ...getMarketsAndOraclesForSubscription(env),
157
+ accountSubscription: {
158
+ type: 'polling',
159
+ accountLoader: bulkAccountLoader,
160
+ },
161
+ });
151
162
  await driftClient.subscribe();
152
163
 
153
- // Set up Clearing House user client
154
- const user = User.from(driftClient, wallet.publicKey);
155
-
156
- //// Check if clearing house account exists for the current wallet
164
+ // Set up user client
165
+ const user = new User({
166
+ driftClient: driftClient,
167
+ userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
168
+ accountSubscription: {
169
+ type: 'polling',
170
+ accountLoader: bulkAccountLoader,
171
+ },
172
+ });
173
+
174
+ //// Check if user account exists for the current wallet
157
175
  const userAccountExists = await user.exists();
158
176
 
159
177
  if (!userAccountExists) {
@@ -171,51 +189,48 @@ const main = async () => {
171
189
  await user.subscribe();
172
190
 
173
191
  // Get current price
174
- const solMarketInfo = Markets.find(
192
+ const solMarketInfo = PerpMarkets[env].find(
175
193
  (market) => market.baseAssetSymbol === 'SOL'
176
194
  );
177
195
 
178
- const currentMarketPrice = calculateReservePrice(
179
- driftClient.getMarket(solMarketInfo.marketIndex)
196
+ const [bid, ask] = calculateBidAskPrice(
197
+ driftClient.getPerpMarketAccount(marketIndex).amm,
198
+ driftClient.getOracleDataForPerpMarket(marketIndex)
180
199
  );
181
200
 
182
- const formattedPrice = convertToNumber(currentMarketPrice, PRICE_PRECISION);
201
+ const formattedBidPrice = convertToNumber(bid, PRICE_PRECISION);
202
+ const formattedAskPrice = convertToNumber(ask, PRICE_PRECISION);
183
203
 
184
- console.log(`Current Market Price is $${formattedPrice}`);
204
+ console.log(
205
+ `Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`
206
+ );
185
207
 
186
208
  // Estimate the slippage for a $5000 LONG trade
187
- const solMarketAccount = driftClient.getMarket(solMarketInfo.marketIndex);
209
+ const solMarketAccount = driftClient.getPerpMarketAccount(
210
+ solMarketInfo.marketIndex
211
+ );
188
212
 
189
213
  const slippage = convertToNumber(
190
214
  calculateTradeSlippage(
191
215
  PositionDirection.LONG,
192
- new BN(5000).mul(QUOTE_PRECISION),
193
- solMarketAccount
216
+ new BN(1).mul(BASE_PRECISION),
217
+ solMarketAccount,
218
+ 'base',
219
+ driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex)
194
220
  )[0],
195
221
  PRICE_PRECISION
196
222
  );
197
223
 
198
- console.log(
199
- `Slippage for a $5000 LONG on the SOL market would be $${slippage}`
200
- );
224
+ console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
201
225
 
202
- // Make a $5000 LONG trade
203
- await driftClient.openPosition(
204
- PositionDirection.LONG,
205
- new BN(5000).mul(QUOTE_PRECISION),
206
- solMarketInfo.marketIndex
226
+ await driftClient.placePerpOrder(
227
+ getMarketOrderParams({
228
+ baseAssetAmount: new BN(1).mul(BASE_PRECISION),
229
+ direction: PositionDirection.LONG,
230
+ marketIndex: solMarketAccount.marketIndex,
231
+ })
207
232
  );
208
- console.log(`LONGED $5000 worth of SOL`);
209
-
210
- // Reduce the position by $2000
211
- await driftClient.openPosition(
212
- PositionDirection.SHORT,
213
- new BN(2000).mul(QUOTE_PRECISION),
214
- solMarketInfo.marketIndex
215
- );
216
-
217
- // Close the rest of the position
218
- await driftClient.closePosition(solMarketInfo.marketIndex);
233
+ console.log(`Placed a 1 SOL-PERP LONG order`);
219
234
  };
220
235
 
221
236
  main();
@@ -11,10 +11,10 @@ const getTokenAddress = (mintAddress, userPubKey) => {
11
11
  return spl_token_1.Token.getAssociatedTokenAddress(new web3_js_1.PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`), spl_token_1.TOKEN_PROGRAM_ID, new web3_js_1.PublicKey(mintAddress), new web3_js_1.PublicKey(userPubKey));
12
12
  };
13
13
  exports.getTokenAddress = getTokenAddress;
14
- const cluster = 'devnet';
14
+ const env = 'devnet';
15
15
  const main = async () => {
16
16
  // Initialize Drift SDK
17
- const sdkConfig = (0, __2.initialize)({ env: cluster });
17
+ const sdkConfig = (0, __2.initialize)({ env });
18
18
  // Set up the Wallet and Provider
19
19
  const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
20
20
  const keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(JSON.parse(privateKey)));
@@ -36,14 +36,14 @@ const main = async () => {
36
36
  connection,
37
37
  wallet: provider.wallet,
38
38
  programID: driftPublicKey,
39
- ...(0, __2.getMarketsAndOraclesForSubscription)(cluster),
39
+ ...(0, __2.getMarketsAndOraclesForSubscription)(env),
40
40
  accountSubscription: {
41
41
  type: 'polling',
42
42
  accountLoader: bulkAccountLoader,
43
43
  },
44
44
  });
45
45
  await driftClient.subscribe();
46
- // Set up Clearing House user client
46
+ // Set up user client
47
47
  const user = new __2.User({
48
48
  driftClient: driftClient,
49
49
  userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
@@ -52,7 +52,7 @@ const main = async () => {
52
52
  accountLoader: bulkAccountLoader,
53
53
  },
54
54
  });
55
- //// Check if clearing house account exists for the current wallet
55
+ //// Check if user account exists for the current wallet
56
56
  const userAccountExists = await user.exists();
57
57
  if (!userAccountExists) {
58
58
  //// Create a Clearing House account by Depositing some USDC ($10,000 in this case)
@@ -62,21 +62,20 @@ const main = async () => {
62
62
  await user.subscribe();
63
63
  // Get current price
64
64
  const solMarketInfo = sdkConfig.PERP_MARKETS.find((market) => market.baseAssetSymbol === 'SOL');
65
- const currentMarketPrice = (0, __2.calculateReservePrice)(driftClient.getPerpMarketAccount(solMarketInfo.marketIndex), undefined);
66
- const formattedPrice = (0, __2.convertToNumber)(currentMarketPrice, __2.PRICE_PRECISION);
67
- console.log(`Current Market Price is $${formattedPrice}`);
65
+ const marketIndex = solMarketInfo.marketIndex;
66
+ const [bid, ask] = (0, __1.calculateBidAskPrice)(driftClient.getPerpMarketAccount(marketIndex).amm, driftClient.getOracleDataForPerpMarket(marketIndex));
67
+ const formattedBidPrice = (0, __2.convertToNumber)(bid, __2.PRICE_PRECISION);
68
+ const formattedAskPrice = (0, __2.convertToNumber)(ask, __2.PRICE_PRECISION);
69
+ console.log(`Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`);
68
70
  // Estimate the slippage for a $5000 LONG trade
69
71
  const solMarketAccount = driftClient.getPerpMarketAccount(solMarketInfo.marketIndex);
70
- const longAmount = new anchor_1.BN(5000).mul(__2.QUOTE_PRECISION);
71
- const slippage = (0, __2.convertToNumber)((0, __2.calculateTradeSlippage)(__2.PositionDirection.LONG, longAmount, solMarketAccount, 'quote', driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex))[0], __2.PRICE_PRECISION);
72
- console.log(`Slippage for a $5000 LONG on the SOL market would be $${slippage}`);
73
- // Make a $5000 LONG trade
74
- await driftClient.openPosition(__2.PositionDirection.LONG, longAmount, solMarketInfo.marketIndex);
75
- console.log(`LONGED $5000 SOL`);
76
- // Reduce the position by $2000
77
- const reduceAmount = new anchor_1.BN(2000).mul(__2.QUOTE_PRECISION);
78
- await driftClient.openPosition(__2.PositionDirection.SHORT, reduceAmount, solMarketInfo.marketIndex);
79
- // Close the rest of the position
80
- await driftClient.closePosition(solMarketInfo.marketIndex);
72
+ const slippage = (0, __2.convertToNumber)((0, __2.calculateTradeSlippage)(__2.PositionDirection.LONG, new anchor_1.BN(1).mul(__1.BASE_PRECISION), solMarketAccount, 'base', driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex))[0], __2.PRICE_PRECISION);
73
+ console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
74
+ await driftClient.placePerpOrder((0, __1.getMarketOrderParams)({
75
+ baseAssetAmount: new anchor_1.BN(1).mul(__1.BASE_PRECISION),
76
+ direction: __2.PositionDirection.LONG,
77
+ marketIndex: solMarketAccount.marketIndex,
78
+ }));
79
+ console.log(`Placed a 1 SOL-PERP LONG order`);
81
80
  };
82
81
  main();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.12.0-beta.0",
2
+ "version": "2.12.0-beta.1",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -8568,6 +8568,11 @@
8568
8568
  "code": 6224,
8569
8569
  "name": "MaxNumberOfUsers",
8570
8570
  "msg": "Max Number Of Users"
8571
+ },
8572
+ {
8573
+ "code": 6225,
8574
+ "name": "InvalidOracleForSettlePnl",
8575
+ "msg": "InvalidOracleForSettlePnl"
8571
8576
  }
8572
8577
  ]
8573
8578
  }
@@ -3,6 +3,7 @@ import { PerpMarketAccount, PositionDirection } from '../types';
3
3
  import { BN } from '@project-serum/anchor';
4
4
  import { AssetType } from './amm';
5
5
  import { OraclePriceData } from '../oracles/types';
6
+ import { DLOB } from '../dlob/DLOB';
6
7
  export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' | 'priceDeltaAsNumber' | 'pctAvg' | 'pctMax' | 'quoteAssetAmount' | 'quoteAssetAmountPeg' | 'acquiredBaseAssetAmount' | 'acquiredQuoteAssetAmount' | 'all';
7
8
  /**
8
9
  * Calculates avg/max slippage (price impact) for candidate trade
@@ -52,3 +53,17 @@ export declare function calculateTradeAcquiredAmounts(direction: PositionDirecti
52
53
  * ]
53
54
  */
54
55
  export declare function calculateTargetPriceTrade(market: PerpMarketAccount, targetPrice: BN, pct?: BN, outputAssetType?: AssetType, oraclePriceData?: OraclePriceData, useSpread?: boolean): [PositionDirection, BN, BN, BN];
56
+ /**
57
+ * Calculates the estimated entry price and price impact of order, in base or quote
58
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
59
+ *
60
+ * @param assetType
61
+ * @param amount
62
+ * @param direction
63
+ * @param market
64
+ * @param oraclePriceData
65
+ * @param dlob
66
+ * @param slot
67
+ * @param minPerpAuctionDuration
68
+ */
69
+ export declare function calculateEstimatedPerpEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number, minPerpAuctionDuration: number): [BN, BN];
package/lib/math/trade.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
3
+ exports.calculateEstimatedPerpEntryPrice = exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
4
4
  const types_1 = require("../types");
5
5
  const anchor_1 = require("@project-serum/anchor");
6
6
  const assert_1 = require("../assert/assert");
@@ -247,3 +247,137 @@ function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAsse
247
247
  }
248
248
  }
249
249
  exports.calculateTargetPriceTrade = calculateTargetPriceTrade;
250
+ /**
251
+ * Calculates the estimated entry price and price impact of order, in base or quote
252
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
253
+ *
254
+ * @param assetType
255
+ * @param amount
256
+ * @param direction
257
+ * @param market
258
+ * @param oraclePriceData
259
+ * @param dlob
260
+ * @param slot
261
+ * @param minPerpAuctionDuration
262
+ */
263
+ function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot, minPerpAuctionDuration) {
264
+ if (amount.eq(numericConstants_1.ZERO)) {
265
+ return [numericConstants_1.ZERO, numericConstants_1.ZERO];
266
+ }
267
+ const takerIsLong = (0, types_2.isVariant)(direction, 'long');
268
+ const limitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, minPerpAuctionDuration);
269
+ const swapDirection = (0, amm_1.getSwapDirection)(assetType, direction);
270
+ const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, oraclePriceData);
271
+ const amm = {
272
+ baseAssetReserve,
273
+ quoteAssetReserve,
274
+ sqrtK: sqrtK,
275
+ pegMultiplier: newPeg,
276
+ };
277
+ const invariant = amm.sqrtK.mul(amm.sqrtK);
278
+ let initialPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
279
+ let cumulativeBaseFilled = numericConstants_1.ZERO;
280
+ let cumulativeQuoteFilled = numericConstants_1.ZERO;
281
+ let limitOrder = limitOrders.next().value;
282
+ if (limitOrder) {
283
+ const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
284
+ initialPrice = takerIsLong
285
+ ? anchor_1.BN.min(limitOrderPrice, initialPrice)
286
+ : anchor_1.BN.max(limitOrderPrice, initialPrice);
287
+ }
288
+ if (assetType === 'base') {
289
+ while (!cumulativeBaseFilled.eq(amount)) {
290
+ const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
291
+ let maxAmmFill;
292
+ if (limitOrderPrice) {
293
+ const newBaseReserves = (0, utils_1.squareRootBN)(invariant
294
+ .mul(numericConstants_1.PRICE_PRECISION)
295
+ .mul(amm.pegMultiplier)
296
+ .div(limitOrderPrice)
297
+ .div(numericConstants_1.PEG_PRECISION));
298
+ // will be zero if the limit order price is better than the amm price
299
+ maxAmmFill = takerIsLong
300
+ ? amm.baseAssetReserve.sub(newBaseReserves)
301
+ : newBaseReserves.sub(amm.baseAssetReserve);
302
+ }
303
+ else {
304
+ maxAmmFill = amount.sub(cumulativeBaseFilled);
305
+ }
306
+ if (maxAmmFill.gt(numericConstants_1.ZERO)) {
307
+ const baseFilled = anchor_1.BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
308
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'base', baseFilled, swapDirection);
309
+ const quoteFilled = (0, amm_1.calculateQuoteAssetAmountSwapped)(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
310
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
311
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
312
+ amm.baseAssetReserve = afterSwapBaseReserves;
313
+ amm.quoteAssetReserve = afterSwapQuoteReserves;
314
+ if (cumulativeBaseFilled.eq(amount)) {
315
+ break;
316
+ }
317
+ }
318
+ const baseFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount.sub(limitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
319
+ const quoteFilled = baseFilled.mul(limitOrderPrice).div(numericConstants_1.BASE_PRECISION);
320
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
321
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
322
+ if (cumulativeBaseFilled.eq(amount)) {
323
+ break;
324
+ }
325
+ limitOrder = limitOrders.next().value;
326
+ }
327
+ }
328
+ else {
329
+ while (!cumulativeQuoteFilled.eq(amount)) {
330
+ const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
331
+ let maxAmmFill;
332
+ if (limitOrderPrice) {
333
+ const newQuoteReserves = (0, utils_1.squareRootBN)(invariant
334
+ .mul(numericConstants_1.PEG_PRECISION)
335
+ .mul(limitOrderPrice)
336
+ .div(amm.pegMultiplier)
337
+ .div(numericConstants_1.PRICE_PRECISION));
338
+ // will be zero if the limit order price is better than the amm price
339
+ maxAmmFill = takerIsLong
340
+ ? newQuoteReserves.sub(amm.quoteAssetReserve)
341
+ : amm.quoteAssetReserve.sub(newQuoteReserves);
342
+ }
343
+ else {
344
+ maxAmmFill = amount.sub(cumulativeQuoteFilled);
345
+ }
346
+ if (maxAmmFill.gt(numericConstants_1.ZERO)) {
347
+ const quoteFilled = anchor_1.BN.min(amount.sub(cumulativeQuoteFilled), maxAmmFill);
348
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'quote', quoteFilled, swapDirection);
349
+ const baseFilled = afterSwapBaseReserves
350
+ .sub(amm.baseAssetReserve)
351
+ .abs();
352
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
353
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
354
+ amm.baseAssetReserve = afterSwapBaseReserves;
355
+ amm.quoteAssetReserve = afterSwapQuoteReserves;
356
+ if (cumulativeQuoteFilled.eq(amount)) {
357
+ break;
358
+ }
359
+ }
360
+ const quoteFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount
361
+ .sub(limitOrder.order.baseAssetAmountFilled)
362
+ .mul(limitOrderPrice)
363
+ .div(numericConstants_1.BASE_PRECISION), amount.sub(cumulativeQuoteFilled));
364
+ const baseFilled = quoteFilled.mul(numericConstants_1.BASE_PRECISION).div(limitOrderPrice);
365
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
366
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
367
+ if (cumulativeQuoteFilled.eq(amount)) {
368
+ break;
369
+ }
370
+ limitOrder = limitOrders.next().value;
371
+ }
372
+ }
373
+ const entryPrice = cumulativeQuoteFilled
374
+ .mul(numericConstants_1.BASE_PRECISION)
375
+ .div(cumulativeBaseFilled);
376
+ const priceImpact = entryPrice
377
+ .sub(initialPrice)
378
+ .mul(numericConstants_1.PRICE_PRECISION)
379
+ .div(initialPrice)
380
+ .abs();
381
+ return [entryPrice, priceImpact];
382
+ }
383
+ exports.calculateEstimatedPerpEntryPrice = calculateEstimatedPerpEntryPrice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.12.0-beta.0",
3
+ "version": "2.12.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -1,9 +1,13 @@
1
1
  import { AnchorProvider, BN } from '@project-serum/anchor';
2
- import { Wallet } from '..';
2
+ import {
3
+ BASE_PRECISION,
4
+ calculateBidAskPrice,
5
+ getMarketOrderParams,
6
+ Wallet,
7
+ } from '..';
3
8
  import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
4
9
  import { Connection, Keypair, PublicKey } from '@solana/web3.js';
5
10
  import {
6
- calculateReservePrice,
7
11
  DriftClient,
8
12
  User,
9
13
  initialize,
@@ -29,11 +33,11 @@ export const getTokenAddress = (
29
33
  );
30
34
  };
31
35
 
32
- const cluster = 'devnet';
36
+ const env = 'devnet';
33
37
 
34
38
  const main = async () => {
35
39
  // Initialize Drift SDK
36
- const sdkConfig = initialize({ env: cluster });
40
+ const sdkConfig = initialize({ env });
37
41
 
38
42
  // Set up the Wallet and Provider
39
43
  const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
@@ -74,7 +78,7 @@ const main = async () => {
74
78
  connection,
75
79
  wallet: provider.wallet,
76
80
  programID: driftPublicKey,
77
- ...getMarketsAndOraclesForSubscription(cluster),
81
+ ...getMarketsAndOraclesForSubscription(env),
78
82
  accountSubscription: {
79
83
  type: 'polling',
80
84
  accountLoader: bulkAccountLoader,
@@ -82,7 +86,7 @@ const main = async () => {
82
86
  });
83
87
  await driftClient.subscribe();
84
88
 
85
- // Set up Clearing House user client
89
+ // Set up user client
86
90
  const user = new User({
87
91
  driftClient: driftClient,
88
92
  userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
@@ -92,7 +96,7 @@ const main = async () => {
92
96
  },
93
97
  });
94
98
 
95
- //// Check if clearing house account exists for the current wallet
99
+ //// Check if user account exists for the current wallet
96
100
  const userAccountExists = await user.exists();
97
101
 
98
102
  if (!userAccountExists) {
@@ -115,54 +119,45 @@ const main = async () => {
115
119
  (market) => market.baseAssetSymbol === 'SOL'
116
120
  );
117
121
 
118
- const currentMarketPrice = calculateReservePrice(
119
- driftClient.getPerpMarketAccount(solMarketInfo.marketIndex),
120
- undefined
122
+ const marketIndex = solMarketInfo.marketIndex;
123
+ const [bid, ask] = calculateBidAskPrice(
124
+ driftClient.getPerpMarketAccount(marketIndex).amm,
125
+ driftClient.getOracleDataForPerpMarket(marketIndex)
121
126
  );
122
127
 
123
- const formattedPrice = convertToNumber(currentMarketPrice, PRICE_PRECISION);
128
+ const formattedBidPrice = convertToNumber(bid, PRICE_PRECISION);
129
+ const formattedAskPrice = convertToNumber(ask, PRICE_PRECISION);
124
130
 
125
- console.log(`Current Market Price is $${formattedPrice}`);
131
+ console.log(
132
+ `Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`
133
+ );
126
134
 
127
135
  // Estimate the slippage for a $5000 LONG trade
128
136
  const solMarketAccount = driftClient.getPerpMarketAccount(
129
137
  solMarketInfo.marketIndex
130
138
  );
131
139
 
132
- const longAmount = new BN(5000).mul(QUOTE_PRECISION);
133
140
  const slippage = convertToNumber(
134
141
  calculateTradeSlippage(
135
142
  PositionDirection.LONG,
136
- longAmount,
143
+ new BN(1).mul(BASE_PRECISION),
137
144
  solMarketAccount,
138
- 'quote',
145
+ 'base',
139
146
  driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex)
140
147
  )[0],
141
148
  PRICE_PRECISION
142
149
  );
143
150
 
144
- console.log(
145
- `Slippage for a $5000 LONG on the SOL market would be $${slippage}`
146
- );
151
+ console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
147
152
 
148
- // Make a $5000 LONG trade
149
- await driftClient.openPosition(
150
- PositionDirection.LONG,
151
- longAmount,
152
- solMarketInfo.marketIndex
153
+ await driftClient.placePerpOrder(
154
+ getMarketOrderParams({
155
+ baseAssetAmount: new BN(1).mul(BASE_PRECISION),
156
+ direction: PositionDirection.LONG,
157
+ marketIndex: solMarketAccount.marketIndex,
158
+ })
153
159
  );
154
- console.log(`LONGED $5000 SOL`);
155
-
156
- // Reduce the position by $2000
157
- const reduceAmount = new BN(2000).mul(QUOTE_PRECISION);
158
- await driftClient.openPosition(
159
- PositionDirection.SHORT,
160
- reduceAmount,
161
- solMarketInfo.marketIndex
162
- );
163
-
164
- // Close the rest of the position
165
- await driftClient.closePosition(solMarketInfo.marketIndex);
160
+ console.log(`Placed a 1 SOL-PERP LONG order`);
166
161
  };
167
162
 
168
163
  main();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.12.0-beta.0",
2
+ "version": "2.12.0-beta.1",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -8568,6 +8568,11 @@
8568
8568
  "code": 6224,
8569
8569
  "name": "MaxNumberOfUsers",
8570
8570
  "msg": "Max Number Of Users"
8571
+ },
8572
+ {
8573
+ "code": 6225,
8574
+ "name": "InvalidOracleForSettlePnl",
8575
+ "msg": "InvalidOracleForSettlePnl"
8571
8576
  }
8572
8577
  ]
8573
8578
  }
package/src/math/trade.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PerpMarketAccount, PositionDirection } from '../types';
1
+ import { MarketType, PerpMarketAccount, 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
+ BASE_PRECISION,
9
10
  } from '../constants/numericConstants';
10
11
  import {
11
12
  calculateBidPrice,
@@ -23,6 +24,7 @@ import {
23
24
  import { squareRootBN } from './utils';
24
25
  import { isVariant } from '../types';
25
26
  import { OraclePriceData } from '../oracles/types';
27
+ import { DLOB } from '../dlob/DLOB';
26
28
 
27
29
  const MAXPCT = new BN(1000); //percentage units are [0,1000] => [0,1]
28
30
 
@@ -349,3 +351,216 @@ export function calculateTargetPriceTrade(
349
351
  return [direction, baseSize, entryPrice, targetPrice];
350
352
  }
351
353
  }
354
+
355
+ /**
356
+ * Calculates the estimated entry price and price impact of order, in base or quote
357
+ * Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
358
+ *
359
+ * @param assetType
360
+ * @param amount
361
+ * @param direction
362
+ * @param market
363
+ * @param oraclePriceData
364
+ * @param dlob
365
+ * @param slot
366
+ * @param minPerpAuctionDuration
367
+ */
368
+ export function calculateEstimatedPerpEntryPrice(
369
+ assetType: AssetType,
370
+ amount: BN,
371
+ direction: PositionDirection,
372
+ market: PerpMarketAccount,
373
+ oraclePriceData: OraclePriceData,
374
+ dlob: DLOB,
375
+ slot: number,
376
+ minPerpAuctionDuration: number
377
+ ): [BN, BN] {
378
+ if (amount.eq(ZERO)) {
379
+ return [ZERO, ZERO];
380
+ }
381
+
382
+ const takerIsLong = isVariant(direction, 'long');
383
+ const limitOrders = dlob[
384
+ takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'
385
+ ](
386
+ market.marketIndex,
387
+ slot,
388
+ MarketType.PERP,
389
+ oraclePriceData,
390
+ minPerpAuctionDuration
391
+ );
392
+
393
+ const swapDirection = getSwapDirection(assetType, direction);
394
+
395
+ const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } =
396
+ calculateUpdatedAMMSpreadReserves(market.amm, direction, oraclePriceData);
397
+ const amm = {
398
+ baseAssetReserve,
399
+ quoteAssetReserve,
400
+ sqrtK: sqrtK,
401
+ pegMultiplier: newPeg,
402
+ };
403
+
404
+ const invariant = amm.sqrtK.mul(amm.sqrtK);
405
+
406
+ let initialPrice = calculatePrice(
407
+ amm.baseAssetReserve,
408
+ amm.quoteAssetReserve,
409
+ amm.pegMultiplier
410
+ );
411
+
412
+ let cumulativeBaseFilled = ZERO;
413
+ let cumulativeQuoteFilled = ZERO;
414
+
415
+ let limitOrder = limitOrders.next().value;
416
+ if (limitOrder) {
417
+ const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
418
+ initialPrice = takerIsLong
419
+ ? BN.min(limitOrderPrice, initialPrice)
420
+ : BN.max(limitOrderPrice, initialPrice);
421
+ }
422
+
423
+ if (assetType === 'base') {
424
+ while (!cumulativeBaseFilled.eq(amount)) {
425
+ const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
426
+
427
+ let maxAmmFill: BN;
428
+ if (limitOrderPrice) {
429
+ const newBaseReserves = squareRootBN(
430
+ invariant
431
+ .mul(PRICE_PRECISION)
432
+ .mul(amm.pegMultiplier)
433
+ .div(limitOrderPrice)
434
+ .div(PEG_PRECISION)
435
+ );
436
+
437
+ // will be zero if the limit order price is better than the amm price
438
+ maxAmmFill = takerIsLong
439
+ ? amm.baseAssetReserve.sub(newBaseReserves)
440
+ : newBaseReserves.sub(amm.baseAssetReserve);
441
+ } else {
442
+ maxAmmFill = amount.sub(cumulativeBaseFilled);
443
+ }
444
+
445
+ if (maxAmmFill.gt(ZERO)) {
446
+ const baseFilled = BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
447
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] =
448
+ calculateAmmReservesAfterSwap(amm, 'base', baseFilled, swapDirection);
449
+
450
+ const quoteFilled = calculateQuoteAssetAmountSwapped(
451
+ amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
452
+ amm.pegMultiplier,
453
+ swapDirection
454
+ );
455
+
456
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
457
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
458
+
459
+ amm.baseAssetReserve = afterSwapBaseReserves;
460
+ amm.quoteAssetReserve = afterSwapQuoteReserves;
461
+
462
+ if (cumulativeBaseFilled.eq(amount)) {
463
+ break;
464
+ }
465
+ }
466
+
467
+ const baseFilled = BN.min(
468
+ limitOrder.order.baseAssetAmount.sub(
469
+ limitOrder.order.baseAssetAmountFilled
470
+ ),
471
+ amount.sub(cumulativeBaseFilled)
472
+ );
473
+ const quoteFilled = baseFilled.mul(limitOrderPrice).div(BASE_PRECISION);
474
+
475
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
476
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
477
+
478
+ if (cumulativeBaseFilled.eq(amount)) {
479
+ break;
480
+ }
481
+
482
+ limitOrder = limitOrders.next().value;
483
+ }
484
+ } else {
485
+ while (!cumulativeQuoteFilled.eq(amount)) {
486
+ const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
487
+
488
+ let maxAmmFill: BN;
489
+ if (limitOrderPrice) {
490
+ const newQuoteReserves = squareRootBN(
491
+ invariant
492
+ .mul(PEG_PRECISION)
493
+ .mul(limitOrderPrice)
494
+ .div(amm.pegMultiplier)
495
+ .div(PRICE_PRECISION)
496
+ );
497
+
498
+ // will be zero if the limit order price is better than the amm price
499
+ maxAmmFill = takerIsLong
500
+ ? newQuoteReserves.sub(amm.quoteAssetReserve)
501
+ : amm.quoteAssetReserve.sub(newQuoteReserves);
502
+ } else {
503
+ maxAmmFill = amount.sub(cumulativeQuoteFilled);
504
+ }
505
+
506
+ if (maxAmmFill.gt(ZERO)) {
507
+ const quoteFilled = BN.min(
508
+ amount.sub(cumulativeQuoteFilled),
509
+ maxAmmFill
510
+ );
511
+ const [afterSwapQuoteReserves, afterSwapBaseReserves] =
512
+ calculateAmmReservesAfterSwap(
513
+ amm,
514
+ 'quote',
515
+ quoteFilled,
516
+ swapDirection
517
+ );
518
+
519
+ const baseFilled = afterSwapBaseReserves
520
+ .sub(amm.baseAssetReserve)
521
+ .abs();
522
+
523
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
524
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
525
+
526
+ amm.baseAssetReserve = afterSwapBaseReserves;
527
+ amm.quoteAssetReserve = afterSwapQuoteReserves;
528
+
529
+ if (cumulativeQuoteFilled.eq(amount)) {
530
+ break;
531
+ }
532
+ }
533
+
534
+ const quoteFilled = BN.min(
535
+ limitOrder.order.baseAssetAmount
536
+ .sub(limitOrder.order.baseAssetAmountFilled)
537
+ .mul(limitOrderPrice)
538
+ .div(BASE_PRECISION),
539
+ amount.sub(cumulativeQuoteFilled)
540
+ );
541
+
542
+ const baseFilled = quoteFilled.mul(BASE_PRECISION).div(limitOrderPrice);
543
+
544
+ cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
545
+ cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
546
+
547
+ if (cumulativeQuoteFilled.eq(amount)) {
548
+ break;
549
+ }
550
+
551
+ limitOrder = limitOrders.next().value;
552
+ }
553
+ }
554
+
555
+ const entryPrice = cumulativeQuoteFilled
556
+ .mul(BASE_PRECISION)
557
+ .div(cumulativeBaseFilled);
558
+
559
+ const priceImpact = entryPrice
560
+ .sub(initialPrice)
561
+ .mul(PRICE_PRECISION)
562
+ .div(initialPrice)
563
+ .abs();
564
+
565
+ return [entryPrice, priceImpact];
566
+ }