@drift-labs/sdk 2.65.0-beta.1 → 2.65.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.
@@ -137,6 +137,9 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
137
137
  precision: new BN(10).pow(NINE),
138
138
  precisionExp: NINE,
139
139
  serumMarket: new PublicKey('DkbVbMhFxswS32xnn1K2UY4aoBugXooBTxdzkWWDWRkH'),
140
+ phoenixMarket: new PublicKey(
141
+ '5LQLfGtqcC5rm2WuGxJf4tjqYmDjsQAbKo2AMLQ8KB7p'
142
+ ),
140
143
  },
141
144
  {
142
145
  symbol: 'PYTH',
@@ -7,6 +7,7 @@ import {
7
7
  calculateSpreadReserves,
8
8
  calculateUpdatedAMM,
9
9
  DLOBNode,
10
+ isVariant,
10
11
  OraclePriceData,
11
12
  PerpMarketAccount,
12
13
  PositionDirection,
@@ -492,6 +493,19 @@ export function uncrossL2(
492
493
 
493
494
  let bidIndex = 0;
494
495
  let askIndex = 0;
496
+ let maxBid: BN;
497
+ let minAsk: BN;
498
+
499
+ const getPriceAndSetBound = (newPrice: BN, direction: PositionDirection) => {
500
+ if (isVariant(direction, 'long')) {
501
+ maxBid = maxBid ? BN.min(maxBid, newPrice) : newPrice;
502
+ return maxBid;
503
+ } else {
504
+ minAsk = minAsk ? BN.max(minAsk, newPrice) : newPrice;
505
+ return minAsk;
506
+ }
507
+ };
508
+
495
509
  while (bidIndex < bids.length || askIndex < asks.length) {
496
510
  const nextBid = cloneL2Level(bids[bidIndex]);
497
511
  const nextAsk = cloneL2Level(asks[askIndex]);
@@ -525,29 +539,51 @@ export function uncrossL2(
525
539
  nextBid.price.gt(referencePrice) &&
526
540
  nextAsk.price.gt(referencePrice)
527
541
  ) {
528
- const newBidPrice = nextAsk.price.sub(grouping);
542
+ let newBidPrice = nextAsk.price.sub(grouping);
543
+ newBidPrice = getPriceAndSetBound(newBidPrice, PositionDirection.LONG);
529
544
  updateLevels(newBidPrice, nextBid, newBids);
530
545
  bidIndex++;
531
546
  } else if (
532
547
  nextAsk.price.lt(referencePrice) &&
533
548
  nextBid.price.lt(referencePrice)
534
549
  ) {
535
- const newAskPrice = nextBid.price.add(grouping);
550
+ let newAskPrice = nextBid.price.add(grouping);
551
+ newAskPrice = getPriceAndSetBound(newAskPrice, PositionDirection.SHORT);
536
552
  updateLevels(newAskPrice, nextAsk, newAsks);
537
553
  askIndex++;
538
554
  } else {
539
- const newBidPrice = referencePrice.sub(grouping);
540
- const newAskPrice = referencePrice.add(grouping);
555
+ let newBidPrice = referencePrice.sub(grouping);
556
+ let newAskPrice = referencePrice.add(grouping);
557
+
558
+ newBidPrice = getPriceAndSetBound(newBidPrice, PositionDirection.LONG);
559
+ newAskPrice = getPriceAndSetBound(newAskPrice, PositionDirection.SHORT);
560
+
541
561
  updateLevels(newBidPrice, nextBid, newBids);
542
562
  updateLevels(newAskPrice, nextAsk, newAsks);
543
563
  bidIndex++;
544
564
  askIndex++;
545
565
  }
546
566
  } else {
547
- newAsks.push(nextAsk);
567
+ if (minAsk && nextAsk.price.lte(minAsk)) {
568
+ const newAskPrice = getPriceAndSetBound(
569
+ nextAsk.price,
570
+ PositionDirection.SHORT
571
+ );
572
+ updateLevels(newAskPrice, nextAsk, newAsks);
573
+ } else {
574
+ newAsks.push(nextAsk);
575
+ }
548
576
  askIndex++;
549
577
 
550
- newBids.push(nextBid);
578
+ if (maxBid && nextBid.price.gte(maxBid)) {
579
+ const newBidPrice = getPriceAndSetBound(
580
+ nextBid.price,
581
+ PositionDirection.LONG
582
+ );
583
+ updateLevels(newBidPrice, nextBid, newBids);
584
+ } else {
585
+ newBids.push(nextBid);
586
+ }
551
587
  bidIndex++;
552
588
  }
553
589
  }
@@ -693,25 +693,36 @@ export class DriftClient {
693
693
  );
694
694
  }
695
695
  } else {
696
- const userAccounts =
697
- (await this.getUserAccountsForAuthority(this.wallet.publicKey)) ?? [];
696
+ let userAccounts = [];
698
697
  let delegatedAccounts = [];
699
698
 
699
+ const userAccountsPromise = this.getUserAccountsForAuthority(
700
+ this.wallet.publicKey
701
+ );
702
+
700
703
  if (this.includeDelegates) {
701
- delegatedAccounts =
702
- (await this.getUserAccountsForDelegate(this.wallet.publicKey)) ?? [];
703
- }
704
+ const delegatedAccountsPromise = this.getUserAccountsForDelegate(
705
+ this.wallet.publicKey
706
+ );
707
+ [userAccounts, delegatedAccounts] = await Promise.all([
708
+ userAccountsPromise,
709
+ delegatedAccountsPromise,
710
+ ]);
704
711
 
705
- for (const account of userAccounts.concat(delegatedAccounts)) {
706
- result =
707
- result &&
708
- (await this.addUser(
709
- account.subAccountId,
710
- account.authority,
711
- account
712
- ));
712
+ !userAccounts && (userAccounts = []);
713
+ !delegatedAccounts && (delegatedAccounts = []);
714
+ } else {
715
+ userAccounts = (await userAccountsPromise) ?? [];
713
716
  }
714
717
 
718
+ const allAccounts = userAccounts.concat(delegatedAccounts);
719
+ const addAllAccountsPromise = allAccounts.map((acc) =>
720
+ this.addUser(acc.subAccountId, acc.authority, acc)
721
+ );
722
+
723
+ const addAllAccountsResults = await Promise.all(addAllAccountsPromise);
724
+ result = addAllAccountsResults.every((res) => !!res);
725
+
715
726
  if (this.activeSubAccountId == undefined) {
716
727
  this.switchActiveUser(
717
728
  userAccounts.concat(delegatedAccounts)[0]?.subAccountId ?? 0,
@@ -305,9 +305,14 @@ export class JupiterClient {
305
305
  onlyDirectRoutes: onlyDirectRoutes.toString(),
306
306
  maxAccounts: maxAccounts.toString(),
307
307
  ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }),
308
- }).toString();
309
- const quote = await (await fetch(`${this.url}/v6/quote?${params}`)).json();
310
- return quote;
308
+ });
309
+ if (swapMode === 'ExactOut') {
310
+ params.delete('maxAccounts');
311
+ }
312
+ const quote = await (
313
+ await fetch(`${this.url}/v6/quote?${params.toString()}`)
314
+ ).json();
315
+ return quote as QuoteResponse;
311
316
  }
312
317
 
313
318
  /**
@@ -13,9 +13,10 @@ import {
13
13
  LPRecord,
14
14
  StateAccount,
15
15
  DLOB,
16
- OneShotUserAccountSubscriber,
17
16
  BN,
18
17
  UserSubscriptionConfig,
18
+ DataAndSlot,
19
+ OneShotUserAccountSubscriber,
19
20
  } from '..';
20
21
 
21
22
  import {
@@ -37,17 +38,33 @@ import { decodeUser } from '../decode/user';
37
38
  export interface UserMapInterface {
38
39
  subscribe(): Promise<void>;
39
40
  unsubscribe(): Promise<void>;
40
- addPubkey(userAccountPublicKey: PublicKey): Promise<void>;
41
+ addPubkey(
42
+ userAccountPublicKey: PublicKey,
43
+ userAccount?: UserAccount,
44
+ slot?: number,
45
+ accountSubscription?: UserSubscriptionConfig
46
+ ): Promise<void>;
41
47
  has(key: string): boolean;
42
48
  get(key: string): User | undefined;
43
- mustGet(key: string): Promise<User>;
49
+ getWithSlot(key: string): DataAndSlot<User> | undefined;
50
+ mustGet(
51
+ key: string,
52
+ accountSubscription?: UserSubscriptionConfig
53
+ ): Promise<User>;
54
+ mustGetWithSlot(
55
+ key: string,
56
+ accountSubscription?: UserSubscriptionConfig
57
+ ): Promise<DataAndSlot<User>>;
44
58
  getUserAuthority(key: string): PublicKey | undefined;
45
59
  updateWithOrderRecord(record: OrderRecord): Promise<void>;
46
60
  values(): IterableIterator<User>;
61
+ valuesWithSlot(): IterableIterator<DataAndSlot<User>>;
62
+ entries(): IterableIterator<[string, User]>;
63
+ entriesWithSlot(): IterableIterator<[string, DataAndSlot<User>]>;
47
64
  }
48
65
 
49
66
  export class UserMap implements UserMapInterface {
50
- private userMap = new Map<string, User>();
67
+ private userMap = new Map<string, DataAndSlot<User>>();
51
68
  driftClient: DriftClient;
52
69
  private connection: Connection;
53
70
  private commitment: Commitment;
@@ -140,6 +157,7 @@ export class UserMap implements UserMapInterface {
140
157
  userAccountPublicKey,
141
158
  accountSubscription: accountSubscription ?? {
142
159
  type: 'custom',
160
+ // OneShotUserAccountSubscriber used here so we don't load up the RPC with AccountSubscribes
143
161
  userAccountSubscriber: new OneShotUserAccountSubscriber(
144
162
  this.driftClient.program,
145
163
  userAccountPublicKey,
@@ -150,7 +168,10 @@ export class UserMap implements UserMapInterface {
150
168
  },
151
169
  });
152
170
  await user.subscribe(userAccount);
153
- this.userMap.set(userAccountPublicKey.toString(), user);
171
+ this.userMap.set(userAccountPublicKey.toString(), {
172
+ data: user,
173
+ slot: slot ?? user.getUserAccountAndSlot()?.slot,
174
+ });
154
175
  }
155
176
 
156
177
  public has(key: string): boolean {
@@ -163,6 +184,9 @@ export class UserMap implements UserMapInterface {
163
184
  * @returns user User | undefined
164
185
  */
165
186
  public get(key: string): User | undefined {
187
+ return this.userMap.get(key)?.data;
188
+ }
189
+ public getWithSlot(key: string): DataAndSlot<User> | undefined {
166
190
  return this.userMap.get(key);
167
191
  }
168
192
 
@@ -183,8 +207,21 @@ export class UserMap implements UserMapInterface {
183
207
  accountSubscription
184
208
  );
185
209
  }
186
- const user = this.userMap.get(key);
187
- return user;
210
+ return this.userMap.get(key).data;
211
+ }
212
+ public async mustGetWithSlot(
213
+ key: string,
214
+ accountSubscription?: UserSubscriptionConfig
215
+ ): Promise<DataAndSlot<User>> {
216
+ if (!this.has(key)) {
217
+ await this.addPubkey(
218
+ new PublicKey(key),
219
+ undefined,
220
+ undefined,
221
+ accountSubscription
222
+ );
223
+ }
224
+ return this.userMap.get(key);
188
225
  }
189
226
 
190
227
  /**
@@ -193,11 +230,11 @@ export class UserMap implements UserMapInterface {
193
230
  * @returns authority PublicKey | undefined
194
231
  */
195
232
  public getUserAuthority(key: string): PublicKey | undefined {
196
- const chUser = this.userMap.get(key);
197
- if (!chUser) {
233
+ const user = this.userMap.get(key);
234
+ if (!user) {
198
235
  return undefined;
199
236
  }
200
- return chUser.getUserAccount().authority;
237
+ return user.data.getUserAccount().authority;
201
238
  }
202
239
 
203
240
  /**
@@ -253,10 +290,24 @@ export class UserMap implements UserMapInterface {
253
290
  }
254
291
  }
255
292
 
256
- public values(): IterableIterator<User> {
293
+ public *values(): IterableIterator<User> {
294
+ for (const dataAndSlot of this.userMap.values()) {
295
+ yield dataAndSlot.data;
296
+ }
297
+ }
298
+ public valuesWithSlot(): IterableIterator<DataAndSlot<User>> {
257
299
  return this.userMap.values();
258
300
  }
259
301
 
302
+ public *entries(): IterableIterator<[string, User]> {
303
+ for (const [key, dataAndSlot] of this.userMap.entries()) {
304
+ yield [key, dataAndSlot.data];
305
+ }
306
+ }
307
+ public entriesWithSlot(): IterableIterator<[string, DataAndSlot<User>]> {
308
+ return this.userMap.entries();
309
+ }
310
+
260
311
  public size(): number {
261
312
  return this.userMap.size;
262
313
  }
@@ -269,15 +320,13 @@ export class UserMap implements UserMapInterface {
269
320
  public getUniqueAuthorities(
270
321
  filterCriteria?: UserFilterCriteria
271
322
  ): PublicKey[] {
272
- const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
273
- (user) => {
274
- let pass = true;
275
- if (filterCriteria && filterCriteria.hasOpenOrders) {
276
- pass = pass && user.getUserAccount().hasOpenOrder;
277
- }
278
- return pass;
323
+ const usersMeetingCriteria = Array.from(this.values()).filter((user) => {
324
+ let pass = true;
325
+ if (filterCriteria && filterCriteria.hasOpenOrders) {
326
+ pass = pass && user.getUserAccount().hasOpenOrder;
279
327
  }
280
- );
328
+ return pass;
329
+ });
281
330
  const userAuths = new Set(
282
331
  usersMeetingCriteria.map((user) =>
283
332
  user.getUserAccount().authority.toBase58()
@@ -345,17 +394,17 @@ export class UserMap implements UserMapInterface {
345
394
  for (const [key, buffer] of programAccountBufferMap.entries()) {
346
395
  if (!this.has(key)) {
347
396
  const userAccount = this.decode('User', buffer);
348
- await this.addPubkey(new PublicKey(key), userAccount);
349
- this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
397
+ await this.addPubkey(new PublicKey(key), userAccount, slot);
398
+ this.get(key).accountSubscriber.updateData(userAccount, slot);
350
399
  } else {
351
400
  const userAccount = this.decode('User', buffer);
352
- this.userMap.get(key).accountSubscriber.updateData(userAccount, slot);
401
+ this.get(key).accountSubscriber.updateData(userAccount, slot);
353
402
  }
354
403
  // give event loop a chance to breathe
355
404
  await new Promise((resolve) => setTimeout(resolve, 0));
356
405
  }
357
406
 
358
- for (const [key, user] of this.userMap.entries()) {
407
+ for (const [key, user] of this.entries()) {
359
408
  if (!programAccountBufferMap.has(key)) {
360
409
  await user.unsubscribe();
361
410
  this.userMap.delete(key);
@@ -375,7 +424,7 @@ export class UserMap implements UserMapInterface {
375
424
  public async unsubscribe() {
376
425
  await this.subscription.unsubscribe();
377
426
 
378
- for (const [key, user] of this.userMap.entries()) {
427
+ for (const [key, user] of this.entries()) {
379
428
  await user.unsubscribe();
380
429
  this.userMap.delete(key);
381
430
  }
@@ -398,11 +447,15 @@ export class UserMap implements UserMapInterface {
398
447
  slot: number
399
448
  ) {
400
449
  this.updateLatestSlot(slot);
401
- if (!this.userMap.has(key)) {
450
+ if (!this.has(key)) {
402
451
  this.addPubkey(new PublicKey(key), userAccount, slot);
403
452
  } else {
404
- const user = this.userMap.get(key);
453
+ const user = this.get(key);
405
454
  user.accountSubscriber.updateData(userAccount, slot);
455
+ this.userMap.set(key, {
456
+ data: user,
457
+ slot,
458
+ });
406
459
  }
407
460
  }
408
461
 
@@ -6613,53 +6613,113 @@ describe('Uncross L2', () => {
6613
6613
  new BN(1).mul(BASE_PRECISION).toString()
6614
6614
  );
6615
6615
  });
6616
-
6616
+
6617
6617
  it('Handles edge case bide and asks with large cross and an overlapping level', () => {
6618
+ const bids = [
6619
+ '104411000',
6620
+ '103835800',
6621
+ '103826259',
6622
+ '103825000',
6623
+ '103822000',
6624
+ '103821500',
6625
+ '103820283',
6626
+ '103816900',
6627
+ '103816000',
6628
+ '103815121',
6629
+ ].map((priceStr) => ({
6630
+ price: new BN(priceStr),
6631
+ size: new BN(1).mul(BASE_PRECISION),
6632
+ sources: { vamm: new BN(1).mul(BASE_PRECISION) },
6633
+ }));
6634
+
6635
+ const asks = [
6636
+ '103822000',
6637
+ '103838354',
6638
+ '103843087',
6639
+ '103843351',
6640
+ '103843880',
6641
+ '103845114',
6642
+ '103846148',
6643
+ '103850100',
6644
+ '103851300',
6645
+ '103854304',
6646
+ ].map((priceStr) => ({
6647
+ price: new BN(priceStr),
6648
+ size: new BN(1).mul(BASE_PRECISION),
6649
+ sources: { vamm: new BN(1).mul(BASE_PRECISION) },
6650
+ }));
6651
+
6652
+ expect(asksAreSortedAsc(asks), 'Input asks are ascending').to.be.true;
6653
+ expect(bidsAreSortedDesc(bids), 'Input bids are descending').to.be.true;
6654
+
6655
+ const oraclePrice = new BN('103649895');
6656
+ const oraclePrice5Min = new BN('103285000');
6657
+ const markPrice5Min = new BN('103371000');
6658
+
6659
+ const groupingSize = new BN('100');
6660
+
6661
+ const userAsks = new Set<string>();
6662
+
6663
+ const { bids: newBids, asks: newAsks } = uncrossL2(
6664
+ bids,
6665
+ asks,
6666
+ oraclePrice,
6667
+ oraclePrice5Min,
6668
+ markPrice5Min,
6669
+ groupingSize,
6670
+ new Set<string>(),
6671
+ userAsks
6672
+ );
6618
6673
 
6674
+ expect(asksAreSortedAsc(newAsks), 'Uncrossed asks are ascending').to.be
6675
+ .true;
6676
+ expect(bidsAreSortedDesc(newBids), 'Uncrossed bids are descending').to.be
6677
+ .true;
6678
+ });
6679
+
6680
+ it('Crossing edge case : top bid and ask have a big cross, following ones dont - shouldnt get uncrossed out of order', () => {
6619
6681
  const bids = [
6620
- "104411000",
6621
- "103835800",
6622
- "103826259",
6623
- "103825000",
6624
- "103822000",
6625
- "103821500",
6626
- "103820283",
6627
- "103816900",
6628
- "103816000",
6629
- "103815121",
6630
- ].map(priceStr => (
6631
- {
6632
- price: new BN(priceStr),
6633
- size: new BN(1).mul(BASE_PRECISION),
6634
- sources: { vamm: new BN(1).mul(BASE_PRECISION) },
6635
- }
6636
- ));
6682
+ '101825900',
6683
+ '101783900',
6684
+ '101783000',
6685
+ '101782600',
6686
+ '101770700',
6687
+ '101770200',
6688
+ '101749857',
6689
+ '101735900',
6690
+ '101729994',
6691
+ '101726900',
6692
+ ].map((priceStr) => ({
6693
+ price: new BN(priceStr),
6694
+ size: new BN(1).mul(BASE_PRECISION),
6695
+ sources: { vamm: new BN(1).mul(BASE_PRECISION) },
6696
+ }));
6637
6697
 
6638
6698
  const asks = [
6639
- "103822000",
6640
- "103838354",
6641
- "103843087",
6642
- "103843351",
6643
- "103843880",
6644
- "103845114",
6645
- "103846148",
6646
- "103850100",
6647
- "103851300",
6648
- "103854304",
6649
- ].map(priceStr => ({
6699
+ '101750700',
6700
+ '101790467',
6701
+ '101793400',
6702
+ '101794116',
6703
+ '101798548',
6704
+ '101799532',
6705
+ '101803500',
6706
+ '101820927',
6707
+ '101823900',
6708
+ '101827638',
6709
+ ].map((priceStr) => ({
6650
6710
  price: new BN(priceStr),
6651
6711
  size: new BN(1).mul(BASE_PRECISION),
6652
6712
  sources: { vamm: new BN(1).mul(BASE_PRECISION) },
6653
6713
  }));
6654
6714
 
6655
- expect(asksAreSortedAsc(asks), "Input asks are ascending").to.be.true;
6656
- expect(bidsAreSortedDesc(bids), "Input bids are descending").to.be.true;
6715
+ expect(asksAreSortedAsc(asks), 'Input asks are ascending').to.be.true;
6716
+ expect(bidsAreSortedDesc(bids), 'Input bids are descending').to.be.true;
6657
6717
 
6658
- const oraclePrice = new BN("103649895");
6659
- const oraclePrice5Min = new BN("103285000");
6660
- const markPrice5Min = new BN("103371000");
6718
+ const oraclePrice = new BN('101711384');
6719
+ const oraclePrice5Min = new BN('101805000');
6720
+ const markPrice5Min = new BN('101867000');
6661
6721
 
6662
- const groupingSize = new BN("100");
6722
+ const groupingSize = new BN('100');
6663
6723
 
6664
6724
  const userAsks = new Set<string>();
6665
6725
 
@@ -6674,7 +6734,9 @@ describe('Uncross L2', () => {
6674
6734
  userAsks
6675
6735
  );
6676
6736
 
6677
- expect(asksAreSortedAsc(newAsks), "Uncrossed asks are ascending").to.be.true;
6678
- expect(bidsAreSortedDesc(newBids), "Uncrossed bids are descending").to.be.true;
6737
+ expect(asksAreSortedAsc(newAsks), 'Uncrossed asks are ascending').to.be
6738
+ .true;
6739
+ expect(bidsAreSortedDesc(newBids), 'Uncrossed bids are descending').to.be
6740
+ .true;
6679
6741
  });
6680
6742
  });