@drift-labs/sdk 0.2.0-master.2 → 0.2.0-master.20

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 (152) hide show
  1. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  2. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  3. package/lib/accounts/bulkUserSubscription.js +0 -1
  4. package/lib/accounts/fetch.d.ts +2 -1
  5. package/lib/accounts/fetch.js +9 -1
  6. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  7. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  8. package/lib/accounts/types.d.ts +14 -1
  9. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  10. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  11. package/lib/addresses/pda.d.ts +1 -0
  12. package/lib/addresses/pda.js +8 -1
  13. package/lib/admin.d.ts +9 -5
  14. package/lib/admin.js +52 -11
  15. package/lib/clearingHouse.d.ts +52 -23
  16. package/lib/clearingHouse.js +727 -197
  17. package/lib/clearingHouseConfig.d.ts +1 -0
  18. package/lib/clearingHouseUser.d.ts +17 -17
  19. package/lib/clearingHouseUser.js +186 -101
  20. package/lib/clearingHouseUserStats.d.ts +18 -0
  21. package/lib/clearingHouseUserStats.js +49 -0
  22. package/lib/clearingHouseUserStatsConfig.d.ts +14 -0
  23. package/lib/clearingHouseUserStatsConfig.js +2 -0
  24. package/lib/config.js +1 -1
  25. package/lib/constants/banks.d.ts +2 -2
  26. package/lib/constants/banks.js +12 -4
  27. package/lib/constants/numericConstants.d.ts +5 -0
  28. package/lib/constants/numericConstants.js +8 -3
  29. package/lib/events/eventList.js +3 -0
  30. package/lib/events/types.d.ts +3 -1
  31. package/lib/events/types.js +2 -0
  32. package/lib/factory/bigNum.d.ts +1 -0
  33. package/lib/factory/bigNum.js +34 -10
  34. package/lib/idl/clearing_house.json +1609 -388
  35. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  36. package/lib/index.d.ts +9 -3
  37. package/lib/index.js +13 -3
  38. package/lib/math/amm.d.ts +1 -0
  39. package/lib/math/amm.js +22 -38
  40. package/lib/math/auction.js +4 -1
  41. package/lib/math/bankBalance.d.ts +7 -1
  42. package/lib/math/bankBalance.js +77 -2
  43. package/lib/math/margin.d.ts +11 -0
  44. package/lib/math/margin.js +72 -0
  45. package/lib/math/market.d.ts +4 -1
  46. package/lib/math/market.js +35 -1
  47. package/lib/math/oracles.d.ts +3 -0
  48. package/lib/math/oracles.js +25 -5
  49. package/lib/math/orders.d.ts +5 -2
  50. package/lib/math/orders.js +53 -12
  51. package/lib/math/position.d.ts +8 -0
  52. package/lib/math/position.js +45 -12
  53. package/lib/math/trade.d.ts +1 -1
  54. package/lib/math/trade.js +7 -10
  55. package/lib/orderParams.d.ts +14 -5
  56. package/lib/orderParams.js +8 -96
  57. package/lib/slot/SlotSubscriber.d.ts +7 -0
  58. package/lib/slot/SlotSubscriber.js +3 -0
  59. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  60. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  61. package/lib/tx/retryTxSender.js +9 -2
  62. package/lib/tx/utils.js +1 -1
  63. package/lib/types.d.ts +233 -26
  64. package/lib/types.js +64 -1
  65. package/lib/util/computeUnits.js +1 -1
  66. package/lib/util/getTokenAddress.d.ts +2 -0
  67. package/lib/util/getTokenAddress.js +9 -0
  68. package/package.json +3 -3
  69. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  70. package/src/accounts/bulkUserSubscription.ts +0 -1
  71. package/src/accounts/fetch.ts +27 -2
  72. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  73. package/src/accounts/types.ts +18 -0
  74. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  75. package/src/addresses/marketAddresses.js +26 -0
  76. package/src/addresses/pda.ts +13 -0
  77. package/src/admin.ts +82 -15
  78. package/src/assert/assert.js +9 -0
  79. package/src/clearingHouse.ts +1224 -319
  80. package/src/clearingHouseConfig.ts +1 -0
  81. package/src/clearingHouseUser.ts +311 -148
  82. package/src/clearingHouseUserStats.ts +75 -0
  83. package/src/clearingHouseUserStatsConfig.ts +18 -0
  84. package/src/config.ts +1 -1
  85. package/src/constants/banks.js +42 -0
  86. package/src/constants/banks.ts +14 -4
  87. package/src/constants/markets.js +42 -0
  88. package/src/constants/numericConstants.js +41 -0
  89. package/src/constants/numericConstants.ts +14 -2
  90. package/src/events/eventList.js +77 -0
  91. package/src/events/eventList.ts +3 -0
  92. package/src/events/eventSubscriber.js +139 -0
  93. package/src/events/fetchLogs.js +50 -0
  94. package/src/events/pollingLogProvider.js +64 -0
  95. package/src/events/sort.js +44 -0
  96. package/src/events/txEventCache.js +71 -0
  97. package/src/events/types.ts +6 -0
  98. package/src/events/webSocketLogProvider.js +41 -0
  99. package/src/examples/makeTradeExample.js +80 -0
  100. package/src/factory/bigNum.js +390 -0
  101. package/src/factory/bigNum.ts +42 -13
  102. package/src/factory/oracleClient.js +20 -0
  103. package/src/idl/clearing_house.json +1609 -388
  104. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  105. package/src/index.ts +9 -3
  106. package/src/math/amm.ts +54 -55
  107. package/src/math/auction.js +42 -0
  108. package/src/math/auction.ts +5 -1
  109. package/src/math/bankBalance.ts +148 -2
  110. package/src/math/conversion.js +11 -0
  111. package/src/math/funding.js +248 -0
  112. package/src/math/margin.ts +124 -0
  113. package/src/math/market.ts +66 -1
  114. package/src/math/oracles.js +26 -0
  115. package/src/math/oracles.ts +42 -5
  116. package/src/math/orders.ts +112 -13
  117. package/src/math/position.ts +64 -9
  118. package/src/math/repeg.js +128 -0
  119. package/src/math/state.js +15 -0
  120. package/src/math/trade.js +253 -0
  121. package/src/math/trade.ts +23 -25
  122. package/src/math/utils.js +0 -1
  123. package/src/oracles/oracleClientCache.js +19 -0
  124. package/src/oracles/pythClient.js +46 -0
  125. package/src/oracles/quoteAssetOracleClient.js +32 -0
  126. package/src/oracles/switchboardClient.js +69 -0
  127. package/src/oracles/types.js +2 -0
  128. package/src/orderParams.js +20 -0
  129. package/src/orderParams.ts +20 -141
  130. package/src/slot/SlotSubscriber.js +39 -0
  131. package/src/slot/SlotSubscriber.ts +11 -1
  132. package/src/token/index.js +38 -0
  133. package/src/tokenFaucet.js +189 -0
  134. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  135. package/src/tx/retryTxSender.ts +11 -3
  136. package/src/tx/types.js +2 -0
  137. package/src/tx/utils.js +17 -0
  138. package/src/tx/utils.ts +1 -1
  139. package/src/types.ts +236 -27
  140. package/src/userName.js +20 -0
  141. package/src/util/computeUnits.js +21 -11
  142. package/src/util/computeUnits.ts +1 -1
  143. package/src/util/getTokenAddress.js +9 -0
  144. package/src/util/getTokenAddress.ts +18 -0
  145. package/src/util/promiseTimeout.js +14 -0
  146. package/src/util/tps.js +27 -0
  147. package/src/wallet.js +35 -0
  148. package/tests/bn/test.ts +10 -0
  149. package/lib/orders.d.ts +0 -8
  150. package/lib/orders.js +0 -142
  151. package/src/orders.ts +0 -251
  152. package/src/util/computeUnits.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { AnchorProvider, BN, Idl, Program } from '@project-serum/anchor';
2
- import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
2
+ import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
3
3
  import {
4
4
  StateAccount,
5
5
  IWallet,
@@ -11,6 +11,11 @@ import {
11
11
  BankAccount,
12
12
  UserBankBalance,
13
13
  MakerInfo,
14
+ TakerInfo,
15
+ OptionalOrderParams,
16
+ DefaultOrderParams,
17
+ OrderType,
18
+ ReferrerInfo,
14
19
  } from './types';
15
20
  import * as anchor from '@project-serum/anchor';
16
21
  import clearingHouseIDL from './idl/clearing_house.json';
@@ -23,9 +28,14 @@ import {
23
28
  Transaction,
24
29
  TransactionInstruction,
25
30
  AccountMeta,
31
+ Keypair,
32
+ LAMPORTS_PER_SOL,
33
+ Signer,
34
+ SystemProgram,
35
+ ComputeBudgetProgram,
26
36
  } from '@solana/web3.js';
27
37
 
28
- import { MockUSDCFaucet } from './mockUSDCFaucet';
38
+ import { TokenFaucet } from './tokenFaucet';
29
39
  import { EventEmitter } from 'events';
30
40
  import StrictEventEmitter from 'strict-event-emitter-types';
31
41
  import {
@@ -33,6 +43,7 @@ import {
33
43
  getMarketPublicKey,
34
44
  getUserAccountPublicKey,
35
45
  getUserAccountPublicKeySync,
46
+ getUserStatsAccountPublicKey,
36
47
  } from './addresses/pda';
37
48
  import {
38
49
  ClearingHouseAccountSubscriber,
@@ -41,7 +52,11 @@ import {
41
52
  } from './accounts/types';
42
53
  import { TxSender } from './tx/types';
43
54
  import { wrapInTx } from './tx/utils';
44
- import { QUOTE_ASSET_BANK_INDEX, ZERO } from './constants/numericConstants';
55
+ import {
56
+ ONE,
57
+ QUOTE_ASSET_BANK_INDEX,
58
+ ZERO,
59
+ } from './constants/numericConstants';
45
60
  import { findDirectionToClose, positionIsAvailable } from './math/position';
46
61
  import { getTokenAmount } from './math/bankBalance';
47
62
  import { DEFAULT_USER_NAME, encodeName } from './userName';
@@ -52,8 +67,9 @@ import { WebSocketClearingHouseAccountSubscriber } from './accounts/webSocketCle
52
67
  import { RetryTxSender } from './tx/retryTxSender';
53
68
  import { ClearingHouseUser } from './clearingHouseUser';
54
69
  import { ClearingHouseUserAccountSubscriptionConfig } from './clearingHouseUserConfig';
55
- import { getMarketOrderParams } from './orderParams';
56
70
  import { getMarketsBanksAndOraclesForSubscription } from './config';
71
+ import { WRAPPED_SOL_MINT } from './constants/banks';
72
+ import { ClearingHouseUserStats } from './clearingHouseUserStats';
57
73
 
58
74
  /**
59
75
  * # ClearingHouse
@@ -66,6 +82,7 @@ export class ClearingHouse {
66
82
  provider: AnchorProvider;
67
83
  opts?: ConfirmOptions;
68
84
  users = new Map<number, ClearingHouseUser>();
85
+ userStats?: ClearingHouseUserStats;
69
86
  activeUserId: number;
70
87
  userAccountSubscriptionConfig: ClearingHouseUserAccountSubscriptionConfig;
71
88
  accountSubscriber: ClearingHouseAccountSubscriber;
@@ -109,6 +126,16 @@ export class ClearingHouse {
109
126
  type: 'websocket',
110
127
  };
111
128
  this.createUsers(userIds, this.userAccountSubscriptionConfig);
129
+ if (config.userStats) {
130
+ this.userStats = new ClearingHouseUserStats({
131
+ clearingHouse: this,
132
+ userStatsAccountPublicKey: getUserStatsAccountPublicKey(
133
+ this.program.programId,
134
+ this.wallet.publicKey
135
+ ),
136
+ accountSubscription: this.userAccountSubscriptionConfig,
137
+ });
138
+ }
112
139
 
113
140
  let marketIndexes = config.marketIndexes;
114
141
  let bankIndexes = config.bankIndexes;
@@ -180,6 +207,9 @@ export class ClearingHouse {
180
207
  const subscribePromises = this.subscribeUsers().concat(
181
208
  this.accountSubscriber.subscribe()
182
209
  );
210
+ if (this.userStats !== undefined) {
211
+ subscribePromises.concat(this.userStats.subscribe());
212
+ }
183
213
  this.isSubscribed = (await Promise.all(subscribePromises)).reduce(
184
214
  (success, prevSuccess) => success && prevSuccess
185
215
  );
@@ -205,6 +235,9 @@ export class ClearingHouse {
205
235
  const unsubscribePromises = this.unsubscribeUsers().concat(
206
236
  this.accountSubscriber.unsubscribe()
207
237
  );
238
+ if (this.userStats !== undefined) {
239
+ unsubscribePromises.concat(this.userStats.unsubscribe());
240
+ }
208
241
  await Promise.all(unsubscribePromises);
209
242
  this.isSubscribed = false;
210
243
  }
@@ -294,6 +327,7 @@ export class ClearingHouse {
294
327
  }
295
328
 
296
329
  this.activeUserId = activeUserId;
330
+ this.userStatsAccountPublicKey = undefined;
297
331
  }
298
332
 
299
333
  public async switchActiveUser(userId: number): Promise<void> {
@@ -312,19 +346,26 @@ export class ClearingHouse {
312
346
 
313
347
  public async initializeUserAccount(
314
348
  userId = 0,
315
- name = DEFAULT_USER_NAME
349
+ name = DEFAULT_USER_NAME,
350
+ referrerInfo?: ReferrerInfo
316
351
  ): Promise<[TransactionSignature, PublicKey]> {
317
352
  const [userAccountPublicKey, initializeUserAccountIx] =
318
- await this.getInitializeUserInstructions(userId, name);
353
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
319
354
 
320
- const tx = new Transaction().add(initializeUserAccountIx);
355
+ const tx = new Transaction();
356
+ if (userId === 0) {
357
+ // not the safest assumption, can explicitly check if user stats account exists if it causes problems
358
+ tx.add(await this.getInitializeUserStatsIx());
359
+ }
360
+ tx.add(initializeUserAccountIx);
321
361
  const { txSig } = await this.txSender.send(tx, [], this.opts);
322
362
  return [txSig, userAccountPublicKey];
323
363
  }
324
364
 
325
365
  async getInitializeUserInstructions(
326
366
  userId = 0,
327
- name = DEFAULT_USER_NAME
367
+ name = DEFAULT_USER_NAME,
368
+ referrerInfo?: ReferrerInfo
328
369
  ): Promise<[PublicKey, TransactionInstruction]> {
329
370
  const userAccountPublicKey = await getUserAccountPublicKey(
330
371
  this.program.programId,
@@ -332,22 +373,51 @@ export class ClearingHouse {
332
373
  userId
333
374
  );
334
375
 
376
+ const remainingAccounts = new Array<AccountMeta>();
377
+ if (referrerInfo !== undefined) {
378
+ remainingAccounts.push({
379
+ pubkey: referrerInfo.referrer,
380
+ isWritable: true,
381
+ isSigner: false,
382
+ });
383
+ remainingAccounts.push({
384
+ pubkey: referrerInfo.referrerStats,
385
+ isWritable: true,
386
+ isSigner: false,
387
+ });
388
+ }
389
+
335
390
  const nameBuffer = encodeName(name);
336
391
  const initializeUserAccountIx =
337
392
  await this.program.instruction.initializeUser(userId, nameBuffer, {
338
393
  accounts: {
339
394
  user: userAccountPublicKey,
395
+ userStats: this.getUserStatsAccountPublicKey(),
340
396
  authority: this.wallet.publicKey,
341
397
  payer: this.wallet.publicKey,
342
398
  rent: anchor.web3.SYSVAR_RENT_PUBKEY,
343
399
  systemProgram: anchor.web3.SystemProgram.programId,
344
400
  state: await this.getStatePublicKey(),
345
401
  },
402
+ remainingAccounts,
346
403
  });
347
404
 
348
405
  return [userAccountPublicKey, initializeUserAccountIx];
349
406
  }
350
407
 
408
+ async getInitializeUserStatsIx(): Promise<TransactionInstruction> {
409
+ return await this.program.instruction.initializeUserStats({
410
+ accounts: {
411
+ userStats: this.getUserStatsAccountPublicKey(),
412
+ authority: this.wallet.publicKey,
413
+ payer: this.wallet.publicKey,
414
+ rent: anchor.web3.SYSVAR_RENT_PUBKEY,
415
+ systemProgram: anchor.web3.SystemProgram.programId,
416
+ state: await this.getStatePublicKey(),
417
+ },
418
+ });
419
+ }
420
+
351
421
  public getUser(userId?: number): ClearingHouseUser {
352
422
  userId = userId ?? this.activeUserId;
353
423
  if (!this.users.has(userId)) {
@@ -360,6 +430,23 @@ export class ClearingHouse {
360
430
  return [...this.users.values()];
361
431
  }
362
432
 
433
+ public getUserStats(): ClearingHouseUserStats {
434
+ return this.userStats;
435
+ }
436
+
437
+ userStatsAccountPublicKey: PublicKey;
438
+ public getUserStatsAccountPublicKey(): PublicKey {
439
+ if (this.userStatsAccountPublicKey) {
440
+ return this.userStatsAccountPublicKey;
441
+ }
442
+
443
+ this.userStatsAccountPublicKey = getUserStatsAccountPublicKey(
444
+ this.program.programId,
445
+ this.wallet.publicKey
446
+ );
447
+ return this.userStatsAccountPublicKey;
448
+ }
449
+
363
450
  public async getUserAccountPublicKey(): Promise<PublicKey> {
364
451
  return this.getUser().userAccountPublicKey;
365
452
  }
@@ -396,6 +483,7 @@ export class ClearingHouse {
396
483
  getRemainingAccounts(params: {
397
484
  writableMarketIndex?: BN;
398
485
  writableBankIndex?: BN;
486
+ readableMarketIndex?: BN;
399
487
  }): AccountMeta[] {
400
488
  const userAccountAndSlot = this.getUserAccountAndSlot();
401
489
  if (!userAccountAndSlot) {
@@ -436,8 +524,7 @@ export class ClearingHouse {
436
524
  marketAccountMap.set(marketIndexNum, {
437
525
  pubkey: marketAccount.pubkey,
438
526
  isSigner: false,
439
- // isWritable: false, // TODO
440
- isWritable: true,
527
+ isWritable: false,
441
528
  });
442
529
  oracleAccountMap.set(marketAccount.pubkey.toString(), {
443
530
  pubkey: marketAccount.amm.oracle,
@@ -447,6 +534,22 @@ export class ClearingHouse {
447
534
  }
448
535
  }
449
536
 
537
+ if (params.readableMarketIndex) {
538
+ const marketAccount = this.getMarketAccount(
539
+ params.readableMarketIndex.toNumber()
540
+ );
541
+ marketAccountMap.set(params.readableMarketIndex.toNumber(), {
542
+ pubkey: marketAccount.pubkey,
543
+ isSigner: false,
544
+ isWritable: true,
545
+ });
546
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
547
+ pubkey: marketAccount.amm.oracle,
548
+ isSigner: false,
549
+ isWritable: false,
550
+ });
551
+ }
552
+
450
553
  if (params.writableMarketIndex) {
451
554
  const marketAccount = this.getMarketAccount(
452
555
  params.writableMarketIndex.toNumber()
@@ -524,6 +627,31 @@ export class ClearingHouse {
524
627
  userId?: number,
525
628
  reduceOnly = false
526
629
  ): Promise<TransactionSignature> {
630
+ const tx = new Transaction();
631
+ const additionalSigners: Array<Signer> = [];
632
+
633
+ const bank = this.getBankAccount(bankIndex);
634
+
635
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
636
+
637
+ const authority = this.wallet.publicKey;
638
+
639
+ const createWSOLTokenAccount =
640
+ isSolBank && collateralAccountPublicKey.equals(authority);
641
+
642
+ if (createWSOLTokenAccount) {
643
+ const { ixs, signers, pubkey } =
644
+ await this.getWrappedSolAccountCreationIxs(amount);
645
+
646
+ collateralAccountPublicKey = pubkey;
647
+
648
+ ixs.forEach((ix) => {
649
+ tx.add(ix);
650
+ });
651
+
652
+ signers.forEach((signer) => additionalSigners.push(signer));
653
+ }
654
+
527
655
  const depositCollateralIx = await this.getDepositInstruction(
528
656
  amount,
529
657
  bankIndex,
@@ -533,9 +661,26 @@ export class ClearingHouse {
533
661
  true
534
662
  );
535
663
 
536
- const tx = new Transaction().add(depositCollateralIx);
664
+ tx.add(depositCollateralIx);
665
+
666
+ // Close the wrapped sol account at the end of the transaction
667
+ if (createWSOLTokenAccount) {
668
+ tx.add(
669
+ Token.createCloseAccountInstruction(
670
+ TOKEN_PROGRAM_ID,
671
+ collateralAccountPublicKey,
672
+ authority,
673
+ authority,
674
+ []
675
+ )
676
+ );
677
+ }
537
678
 
538
- const { txSig } = await this.txSender.send(tx);
679
+ const { txSig } = await this.txSender.send(
680
+ tx,
681
+ additionalSigners,
682
+ this.opts
683
+ );
539
684
  return txSig;
540
685
  }
541
686
 
@@ -561,13 +706,19 @@ export class ClearingHouse {
561
706
  writableBankIndex: bankIndex,
562
707
  });
563
708
  } else {
564
- remainingAccounts = [
565
- {
566
- pubkey: this.getBankAccount(bankIndex).pubkey,
709
+ const bankAccount = this.getBankAccount(bankIndex);
710
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
711
+ remainingAccounts.push({
712
+ pubkey: bankAccount.oracle,
567
713
  isSigner: false,
568
- isWritable: true,
569
- },
570
- ];
714
+ isWritable: false,
715
+ });
716
+ }
717
+ remainingAccounts.push({
718
+ pubkey: bankAccount.pubkey,
719
+ isSigner: false,
720
+ isWritable: true,
721
+ });
571
722
  }
572
723
 
573
724
  const bank = this.getBankAccount(bankIndex);
@@ -582,6 +733,7 @@ export class ClearingHouse {
582
733
  bank: bank.pubkey,
583
734
  bankVault: bank.vault,
584
735
  user: userAccountPublicKey,
736
+ userStats: this.getUserStatsAccountPublicKey(),
585
737
  userTokenAccount: userTokenAccount,
586
738
  authority: this.wallet.publicKey,
587
739
  tokenProgram: TOKEN_PROGRAM_ID,
@@ -591,12 +743,119 @@ export class ClearingHouse {
591
743
  );
592
744
  }
593
745
 
746
+ private async checkIfAccountExists(account: PublicKey) {
747
+ try {
748
+ const accountInfo = await this.connection.getAccountInfo(account);
749
+ return accountInfo && true;
750
+ } catch (e) {
751
+ // Doesn't already exist
752
+ return false;
753
+ }
754
+ }
755
+
756
+ private async getSolWithdrawalIxs(
757
+ bankIndex: BN,
758
+ amount: BN
759
+ ): Promise<{
760
+ ixs: anchor.web3.TransactionInstruction[];
761
+ signers: Signer[];
762
+ pubkey: PublicKey;
763
+ }> {
764
+ const result = {
765
+ ixs: [],
766
+ signers: [],
767
+ pubkey: PublicKey.default,
768
+ };
769
+
770
+ // Create a temporary wrapped SOL account to store the SOL that we're withdrawing
771
+
772
+ const authority = this.wallet.publicKey;
773
+
774
+ const { ixs, signers, pubkey } = await this.getWrappedSolAccountCreationIxs(
775
+ amount
776
+ );
777
+ result.pubkey = pubkey;
778
+
779
+ ixs.forEach((ix) => {
780
+ result.ixs.push(ix);
781
+ });
782
+
783
+ signers.forEach((ix) => {
784
+ result.signers.push(ix);
785
+ });
786
+
787
+ const withdrawIx = await this.getWithdrawIx(
788
+ amount,
789
+ bankIndex,
790
+ pubkey,
791
+ true
792
+ );
793
+
794
+ result.ixs.push(withdrawIx);
795
+
796
+ result.ixs.push(
797
+ Token.createCloseAccountInstruction(
798
+ TOKEN_PROGRAM_ID,
799
+ pubkey,
800
+ authority,
801
+ authority,
802
+ []
803
+ )
804
+ );
805
+
806
+ return result;
807
+ }
808
+
809
+ private async getWrappedSolAccountCreationIxs(amount: BN): Promise<{
810
+ ixs: anchor.web3.TransactionInstruction[];
811
+ signers: Signer[];
812
+ pubkey: PublicKey;
813
+ }> {
814
+ const wrappedSolAccount = new Keypair();
815
+
816
+ const result = {
817
+ ixs: [],
818
+ signers: [],
819
+ pubkey: wrappedSolAccount.publicKey,
820
+ };
821
+
822
+ const rentSpaceLamports = new BN(LAMPORTS_PER_SOL / 100);
823
+
824
+ const depositAmountLamports = amount.add(rentSpaceLamports);
825
+
826
+ const authority = this.wallet.publicKey;
827
+
828
+ result.ixs.push(
829
+ SystemProgram.createAccount({
830
+ fromPubkey: authority,
831
+ newAccountPubkey: wrappedSolAccount.publicKey,
832
+ lamports: depositAmountLamports.toNumber(),
833
+ space: 165,
834
+ programId: TOKEN_PROGRAM_ID,
835
+ })
836
+ );
837
+
838
+ result.ixs.push(
839
+ Token.createInitAccountInstruction(
840
+ TOKEN_PROGRAM_ID,
841
+ WRAPPED_SOL_MINT,
842
+ wrappedSolAccount.publicKey,
843
+ authority
844
+ )
845
+ );
846
+
847
+ result.signers.push(wrappedSolAccount);
848
+
849
+ return result;
850
+ }
851
+
594
852
  /**
595
853
  * Creates the Clearing House User account for a user, and deposits some initial collateral
596
- * @param userId
597
- * @param name
598
854
  * @param amount
599
855
  * @param userTokenAccount
856
+ * @param bankIndex
857
+ * @param userId
858
+ * @param name
600
859
  * @param fromUserId
601
860
  * @returns
602
861
  */
@@ -606,10 +865,40 @@ export class ClearingHouse {
606
865
  bankIndex = new BN(0),
607
866
  userId = 0,
608
867
  name = DEFAULT_USER_NAME,
609
- fromUserId?: number
868
+ fromUserId?: number,
869
+ referrerInfo?: ReferrerInfo
610
870
  ): Promise<[TransactionSignature, PublicKey]> {
611
871
  const [userAccountPublicKey, initializeUserAccountIx] =
612
- await this.getInitializeUserInstructions(userId, name);
872
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
873
+
874
+ const additionalSigners: Array<Signer> = [];
875
+
876
+ const bank = this.getBankAccount(bankIndex);
877
+
878
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
879
+
880
+ const tx = new Transaction();
881
+
882
+ const authority = this.wallet.publicKey;
883
+
884
+ const createWSOLTokenAccount =
885
+ isSolBank && userTokenAccount.equals(authority);
886
+
887
+ if (createWSOLTokenAccount) {
888
+ const {
889
+ ixs: startIxs,
890
+ signers,
891
+ pubkey,
892
+ } = await this.getWrappedSolAccountCreationIxs(amount);
893
+
894
+ userTokenAccount = pubkey;
895
+
896
+ startIxs.forEach((ix) => {
897
+ tx.add(ix);
898
+ });
899
+
900
+ signers.forEach((signer) => additionalSigners.push(signer));
901
+ }
613
902
 
614
903
  const depositCollateralIx =
615
904
  fromUserId != null
@@ -623,11 +912,29 @@ export class ClearingHouse {
623
912
  false
624
913
  );
625
914
 
626
- const tx = new Transaction()
627
- .add(initializeUserAccountIx)
628
- .add(depositCollateralIx);
915
+ if (userId === 0) {
916
+ tx.add(await this.getInitializeUserStatsIx());
917
+ }
918
+ tx.add(initializeUserAccountIx).add(depositCollateralIx);
919
+
920
+ // Close the wrapped sol account at the end of the transaction
921
+ if (createWSOLTokenAccount) {
922
+ tx.add(
923
+ Token.createCloseAccountInstruction(
924
+ TOKEN_PROGRAM_ID,
925
+ userTokenAccount,
926
+ authority,
927
+ authority,
928
+ []
929
+ )
930
+ );
931
+ }
629
932
 
630
- const { txSig } = await this.txSender.send(tx, []);
933
+ const { txSig } = await this.txSender.send(
934
+ tx,
935
+ additionalSigners,
936
+ this.opts
937
+ );
631
938
 
632
939
  return [txSig, userAccountPublicKey];
633
940
  }
@@ -635,32 +942,35 @@ export class ClearingHouse {
635
942
  public async initializeUserAccountForDevnet(
636
943
  userId = 0,
637
944
  name = DEFAULT_USER_NAME,
638
- mockUSDCFaucet: MockUSDCFaucet,
639
- amount: BN
945
+ bankIndex: BN,
946
+ tokenFaucet: TokenFaucet,
947
+ amount: BN,
948
+ referrerInfo?: ReferrerInfo
640
949
  ): Promise<[TransactionSignature, PublicKey]> {
641
950
  const [associateTokenPublicKey, createAssociatedAccountIx, mintToIx] =
642
- await mockUSDCFaucet.createAssociatedTokenAccountAndMintToInstructions(
951
+ await tokenFaucet.createAssociatedTokenAccountAndMintToInstructions(
643
952
  this.wallet.publicKey,
644
953
  amount
645
954
  );
646
955
 
647
956
  const [userAccountPublicKey, initializeUserAccountIx] =
648
- await this.getInitializeUserInstructions(userId, name);
957
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
649
958
 
650
959
  const depositCollateralIx = await this.getDepositInstruction(
651
960
  amount,
652
- new BN(0),
961
+ bankIndex,
653
962
  associateTokenPublicKey,
654
963
  userId,
655
964
  false,
656
965
  false
657
966
  );
658
967
 
659
- const tx = new Transaction()
660
- .add(createAssociatedAccountIx)
661
- .add(mintToIx)
662
- .add(initializeUserAccountIx)
663
- .add(depositCollateralIx);
968
+ const tx = new Transaction().add(createAssociatedAccountIx).add(mintToIx);
969
+
970
+ if (userId === 0) {
971
+ tx.add(await this.getInitializeUserStatsIx());
972
+ }
973
+ tx.add(initializeUserAccountIx).add(depositCollateralIx);
664
974
 
665
975
  const txSig = await this.program.provider.sendAndConfirm(tx, []);
666
976
 
@@ -673,16 +983,56 @@ export class ClearingHouse {
673
983
  userTokenAccount: PublicKey,
674
984
  reduceOnly = false
675
985
  ): Promise<TransactionSignature> {
676
- const { txSig } = await this.txSender.send(
677
- wrapInTx(
678
- await this.getWithdrawIx(
679
- amount,
680
- bankIndex,
986
+ const tx = new Transaction();
987
+ const additionalSigners: Array<Signer> = [];
988
+
989
+ const bank = this.getBankAccount(bankIndex);
990
+
991
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
992
+
993
+ const authority = this.wallet.publicKey;
994
+
995
+ const createWSOLTokenAccount =
996
+ isSolBank && userTokenAccount.equals(authority);
997
+
998
+ if (createWSOLTokenAccount) {
999
+ const { ixs, signers, pubkey } =
1000
+ await this.getWrappedSolAccountCreationIxs(amount);
1001
+
1002
+ userTokenAccount = pubkey;
1003
+
1004
+ ixs.forEach((ix) => {
1005
+ tx.add(ix);
1006
+ });
1007
+
1008
+ signers.forEach((signer) => additionalSigners.push(signer));
1009
+ }
1010
+
1011
+ const withdrawCollateral = await this.getWithdrawIx(
1012
+ amount,
1013
+ bank.bankIndex,
1014
+ userTokenAccount,
1015
+ reduceOnly
1016
+ );
1017
+
1018
+ tx.add(withdrawCollateral);
1019
+
1020
+ // Close the wrapped sol account at the end of the transaction
1021
+ if (createWSOLTokenAccount) {
1022
+ tx.add(
1023
+ Token.createCloseAccountInstruction(
1024
+ TOKEN_PROGRAM_ID,
681
1025
  userTokenAccount,
682
- reduceOnly
1026
+ authority,
1027
+ authority,
1028
+ []
683
1029
  )
684
- ),
685
- [],
1030
+ );
1031
+ }
1032
+
1033
+ const { txSig } = await this.txSender.send(
1034
+ tx,
1035
+ additionalSigners,
686
1036
  this.opts
687
1037
  );
688
1038
  return txSig;
@@ -713,6 +1063,7 @@ export class ClearingHouse {
713
1063
  bankVault: bank.vault,
714
1064
  bankVaultAuthority: bank.vaultAuthority,
715
1065
  user: userAccountPublicKey,
1066
+ userStats: this.getUserStatsAccountPublicKey(),
716
1067
  userTokenAccount: userTokenAccount,
717
1068
  authority: this.wallet.publicKey,
718
1069
  tokenProgram: TOKEN_PROGRAM_ID,
@@ -764,6 +1115,7 @@ export class ClearingHouse {
764
1115
  authority: this.wallet.publicKey,
765
1116
  fromUser,
766
1117
  toUser,
1118
+ userStats: this.getUserStatsAccountPublicKey(),
767
1119
  state: await this.getStatePublicKey(),
768
1120
  },
769
1121
  remainingAccounts,
@@ -792,212 +1144,245 @@ export class ClearingHouse {
792
1144
  });
793
1145
  }
794
1146
 
795
- public async openPosition(
796
- direction: PositionDirection,
797
- amount: BN,
798
- marketIndex: BN,
799
- limitPrice?: BN
800
- ): Promise<TransactionSignature> {
801
- return await this.placeAndTake(
802
- getMarketOrderParams(
803
- marketIndex,
804
- direction,
805
- ZERO,
806
- amount,
807
- false,
808
- limitPrice
809
- )
810
- );
811
- }
812
-
813
- public async placeOrder(
814
- orderParams: OrderParams
1147
+ public async settleLP(
1148
+ settleeUserAccountPublicKey: PublicKey,
1149
+ marketIndex: BN
815
1150
  ): Promise<TransactionSignature> {
816
- const { txSig, slot } = await this.txSender.send(
817
- wrapInTx(await this.getPlaceOrderIx(orderParams)),
1151
+ const { txSig } = await this.txSender.send(
1152
+ wrapInTx(await this.settleLPIx(settleeUserAccountPublicKey, marketIndex)),
818
1153
  [],
819
1154
  this.opts
820
1155
  );
821
- this.marketLastSlotCache.set(orderParams.marketIndex.toNumber(), slot);
822
1156
  return txSig;
823
1157
  }
824
1158
 
825
- public async getPlaceOrderIx(
826
- orderParams: OrderParams
1159
+ public async settleLPIx(
1160
+ settleeUserAccountPublicKey: PublicKey,
1161
+ marketIndex: BN
827
1162
  ): Promise<TransactionInstruction> {
828
- const userAccountPublicKey = await this.getUserAccountPublicKey();
1163
+ const settleeUserAccount = (await this.program.account.user.fetch(
1164
+ settleeUserAccountPublicKey
1165
+ )) as UserAccount;
1166
+ const userPositions = settleeUserAccount.positions;
1167
+ const remainingAccounts = [];
1168
+
1169
+ let foundMarket = false;
1170
+ for (const position of userPositions) {
1171
+ if (!positionIsAvailable(position)) {
1172
+ const marketPublicKey = await getMarketPublicKey(
1173
+ this.program.programId,
1174
+ position.marketIndex
1175
+ );
1176
+ remainingAccounts.push({
1177
+ pubkey: marketPublicKey,
1178
+ isWritable: true,
1179
+ isSigner: false,
1180
+ });
829
1181
 
830
- const priceOracle = this.getMarketAccount(orderParams.marketIndex).amm
831
- .oracle;
1182
+ if (marketIndex.eq(position.marketIndex)) {
1183
+ foundMarket = true;
1184
+ }
1185
+ }
1186
+ }
832
1187
 
833
- const remainingAccounts = this.getRemainingAccounts({
834
- writableMarketIndex: orderParams.marketIndex,
835
- });
1188
+ if (!foundMarket) {
1189
+ console.log(
1190
+ 'Warning: lp is not in the market specified -- tx will likely fail'
1191
+ );
1192
+ }
836
1193
 
837
- return await this.program.instruction.placeOrder(orderParams, {
1194
+ return this.program.instruction.settleLp(marketIndex, {
838
1195
  accounts: {
839
1196
  state: await this.getStatePublicKey(),
840
- user: userAccountPublicKey,
841
- authority: this.wallet.publicKey,
842
- oracle: priceOracle,
1197
+ user: settleeUserAccountPublicKey,
843
1198
  },
844
- remainingAccounts,
1199
+ remainingAccounts: remainingAccounts,
845
1200
  });
846
1201
  }
847
1202
 
848
- public async expireOrders(
849
- userAccountPublicKey: PublicKey
1203
+ public async removeLiquidity(
1204
+ marketIndex: BN,
1205
+ sharesToBurn?: BN
850
1206
  ): Promise<TransactionSignature> {
851
1207
  const { txSig } = await this.txSender.send(
852
- wrapInTx(await this.getExpireOrdersIx(userAccountPublicKey)),
1208
+ wrapInTx(await this.getRemoveLiquidityIx(marketIndex, sharesToBurn)),
853
1209
  [],
854
1210
  this.opts
855
1211
  );
856
1212
  return txSig;
857
1213
  }
858
1214
 
859
- public async getExpireOrdersIx(
860
- userAccountPublicKey: PublicKey
1215
+ public async getRemoveLiquidityIx(
1216
+ marketIndex: BN,
1217
+ sharesToBurn?: BN
861
1218
  ): Promise<TransactionInstruction> {
862
- const fillerPublicKey = await this.getUserAccountPublicKey();
1219
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1220
+
1221
+ const remainingAccounts = this.getRemainingAccounts({
1222
+ writableMarketIndex: marketIndex,
1223
+ });
1224
+
1225
+ if (sharesToBurn == undefined) {
1226
+ const userAccount = this.getUserAccount();
1227
+ const marketPosition = userAccount.positions.filter((position) =>
1228
+ position.marketIndex.eq(marketIndex)
1229
+ )[0];
1230
+ sharesToBurn = marketPosition.lpShares;
1231
+ console.log('burning lp shares:', sharesToBurn.toString());
1232
+ }
863
1233
 
864
- return await this.program.instruction.expireOrders({
1234
+ return this.program.instruction.removeLiquidity(sharesToBurn, marketIndex, {
865
1235
  accounts: {
866
1236
  state: await this.getStatePublicKey(),
867
- filler: fillerPublicKey,
868
1237
  user: userAccountPublicKey,
869
1238
  authority: this.wallet.publicKey,
870
1239
  },
1240
+ remainingAccounts: remainingAccounts,
871
1241
  });
872
1242
  }
873
1243
 
874
- public async updateAMMs(marketIndexes: BN[]): Promise<TransactionSignature> {
875
- const { txSig } = await this.txSender.send(
876
- wrapInTx(await this.getUpdateAMMsIx(marketIndexes)),
1244
+ public async addLiquidity(
1245
+ amount: BN,
1246
+ marketIndex: BN
1247
+ ): Promise<TransactionSignature> {
1248
+ const { txSig, slot } = await this.txSender.send(
1249
+ wrapInTx(await this.getAddLiquidityIx(amount, marketIndex)),
877
1250
  [],
878
1251
  this.opts
879
1252
  );
1253
+ this.marketLastSlotCache.set(marketIndex.toNumber(), slot);
880
1254
  return txSig;
881
1255
  }
882
1256
 
883
- public async getUpdateAMMsIx(
884
- marketIndexes: BN[]
1257
+ public async getAddLiquidityIx(
1258
+ amount: BN,
1259
+ marketIndex: BN
885
1260
  ): Promise<TransactionInstruction> {
886
- for (let i = marketIndexes.length; i < 5; i++) {
887
- marketIndexes.push(new BN(100));
888
- }
889
- const marketAccountInfos = [];
890
- const oracleAccountInfos = [];
891
- for (const marketIndex of marketIndexes) {
892
- if (!marketIndex.eq(new BN(100))) {
893
- const market = this.getMarketAccount(marketIndex);
894
- marketAccountInfos.push({
895
- pubkey: market.pubkey,
896
- isWritable: true,
897
- isSigner: false,
898
- });
899
- oracleAccountInfos.push({
900
- pubkey: market.amm.oracle,
901
- isWritable: false,
902
- isSigner: false,
903
- });
904
- }
905
- }
906
- const remainingAccounts = oracleAccountInfos.concat(marketAccountInfos);
1261
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1262
+ const remainingAccounts = this.getRemainingAccounts({
1263
+ writableMarketIndex: marketIndex,
1264
+ });
907
1265
 
908
- return await this.program.instruction.updateAmms(marketIndexes, {
1266
+ return this.program.instruction.addLiquidity(amount, marketIndex, {
909
1267
  accounts: {
910
1268
  state: await this.getStatePublicKey(),
1269
+ user: userAccountPublicKey,
911
1270
  authority: this.wallet.publicKey,
912
1271
  },
913
- remainingAccounts,
1272
+ remainingAccounts: remainingAccounts,
914
1273
  });
915
1274
  }
916
1275
 
917
- public async cancelOrder(orderId?: BN): Promise<TransactionSignature> {
918
- const { txSig } = await this.txSender.send(
919
- wrapInTx(await this.getCancelOrderIx(orderId)),
1276
+ public async openPosition(
1277
+ direction: PositionDirection,
1278
+ amount: BN,
1279
+ marketIndex: BN,
1280
+ limitPrice?: BN
1281
+ ): Promise<TransactionSignature> {
1282
+ return await this.placeAndTake({
1283
+ orderType: OrderType.MARKET,
1284
+ marketIndex,
1285
+ direction,
1286
+ baseAssetAmount: amount,
1287
+ price: limitPrice,
1288
+ });
1289
+ }
1290
+
1291
+ public async placeOrder(
1292
+ orderParams: OptionalOrderParams
1293
+ ): Promise<TransactionSignature> {
1294
+ const { txSig, slot } = await this.txSender.send(
1295
+ wrapInTx(await this.getPlaceOrderIx(orderParams)),
920
1296
  [],
921
1297
  this.opts
922
1298
  );
1299
+ this.marketLastSlotCache.set(orderParams.marketIndex.toNumber(), slot);
923
1300
  return txSig;
924
1301
  }
925
1302
 
926
- public async getCancelOrderIx(orderId?: BN): Promise<TransactionInstruction> {
1303
+ getOrderParams(optionalOrderParams: OptionalOrderParams): OrderParams {
1304
+ return Object.assign({}, DefaultOrderParams, optionalOrderParams);
1305
+ }
1306
+
1307
+ public async getPlaceOrderIx(
1308
+ orderParams: OptionalOrderParams
1309
+ ): Promise<TransactionInstruction> {
1310
+ orderParams = this.getOrderParams(orderParams);
927
1311
  const userAccountPublicKey = await this.getUserAccountPublicKey();
928
1312
 
929
- const remainingAccounts = this.getRemainingAccounts({});
1313
+ const remainingAccounts = this.getRemainingAccounts({
1314
+ readableMarketIndex: orderParams.marketIndex,
1315
+ });
930
1316
 
931
- return await this.program.instruction.cancelOrder(orderId ?? null, {
1317
+ return await this.program.instruction.placeOrder(orderParams, {
932
1318
  accounts: {
933
1319
  state: await this.getStatePublicKey(),
934
1320
  user: userAccountPublicKey,
1321
+ userStats: this.getUserStatsAccountPublicKey(),
935
1322
  authority: this.wallet.publicKey,
936
1323
  },
937
1324
  remainingAccounts,
938
1325
  });
939
1326
  }
940
1327
 
941
- public async cancelOrderByUserId(
942
- userOrderId: number
943
- ): Promise<TransactionSignature> {
1328
+ public async updateAMMs(marketIndexes: BN[]): Promise<TransactionSignature> {
944
1329
  const { txSig } = await this.txSender.send(
945
- wrapInTx(await this.getCancelOrderByUserIdIx(userOrderId)),
1330
+ wrapInTx(await this.getUpdateAMMsIx(marketIndexes)),
946
1331
  [],
947
1332
  this.opts
948
1333
  );
949
1334
  return txSig;
950
1335
  }
951
1336
 
952
- public async getCancelOrderByUserIdIx(
953
- userOrderId: number
1337
+ public async getUpdateAMMsIx(
1338
+ marketIndexes: BN[]
954
1339
  ): Promise<TransactionInstruction> {
955
- const userAccountPublicKey = await this.getUserAccountPublicKey();
956
-
957
- const order = this.getOrderByUserId(userOrderId);
958
- const oracle = this.getMarketAccount(order.marketIndex).amm.oracle;
959
-
960
- const remainingAccounts = this.getRemainingAccounts({});
1340
+ for (let i = marketIndexes.length; i < 5; i++) {
1341
+ marketIndexes.push(new BN(100));
1342
+ }
1343
+ const marketAccountInfos = [];
1344
+ const oracleAccountInfos = [];
1345
+ for (const marketIndex of marketIndexes) {
1346
+ if (!marketIndex.eq(new BN(100))) {
1347
+ const market = this.getMarketAccount(marketIndex);
1348
+ marketAccountInfos.push({
1349
+ pubkey: market.pubkey,
1350
+ isWritable: true,
1351
+ isSigner: false,
1352
+ });
1353
+ oracleAccountInfos.push({
1354
+ pubkey: market.amm.oracle,
1355
+ isWritable: false,
1356
+ isSigner: false,
1357
+ });
1358
+ }
1359
+ }
1360
+ const remainingAccounts = oracleAccountInfos.concat(marketAccountInfos);
961
1361
 
962
- return await this.program.instruction.cancelOrderByUserId(userOrderId, {
1362
+ return await this.program.instruction.updateAmms(marketIndexes, {
963
1363
  accounts: {
964
1364
  state: await this.getStatePublicKey(),
965
- user: userAccountPublicKey,
966
1365
  authority: this.wallet.publicKey,
967
- oracle,
968
1366
  },
969
1367
  remainingAccounts,
970
1368
  });
971
1369
  }
972
1370
 
973
- public async cancelAllOrders(
974
- bestEffort?: boolean
975
- ): Promise<TransactionSignature> {
1371
+ public async cancelOrder(orderId?: BN): Promise<TransactionSignature> {
976
1372
  const { txSig } = await this.txSender.send(
977
- wrapInTx(await this.getCancelAllOrdersIx(bestEffort)),
1373
+ wrapInTx(await this.getCancelOrderIx(orderId)),
978
1374
  [],
979
1375
  this.opts
980
1376
  );
981
1377
  return txSig;
982
1378
  }
983
1379
 
984
- public async getCancelAllOrdersIx(
985
- bestEffort?: boolean
986
- ): Promise<TransactionInstruction> {
1380
+ public async getCancelOrderIx(orderId?: BN): Promise<TransactionInstruction> {
987
1381
  const userAccountPublicKey = await this.getUserAccountPublicKey();
988
1382
 
989
1383
  const remainingAccounts = this.getRemainingAccounts({});
990
1384
 
991
- for (const order of this.getUserAccount().orders) {
992
- const oracle = this.getMarketAccount(order.marketIndex).amm.oracle;
993
- remainingAccounts.push({
994
- pubkey: oracle,
995
- isWritable: false,
996
- isSigner: false,
997
- });
998
- }
999
-
1000
- return await this.program.instruction.cancelAllOrders(bestEffort, {
1385
+ return await this.program.instruction.cancelOrder(orderId ?? null, {
1001
1386
  accounts: {
1002
1387
  state: await this.getStatePublicKey(),
1003
1388
  user: userAccountPublicKey,
@@ -1007,67 +1392,54 @@ export class ClearingHouse {
1007
1392
  });
1008
1393
  }
1009
1394
 
1010
- public async cancelOrdersByMarketAndSide(
1011
- bestEffort?: boolean,
1012
- marketIndexOnly?: BN,
1013
- directionOnly?: PositionDirection
1395
+ public async cancelOrderByUserId(
1396
+ userOrderId: number
1014
1397
  ): Promise<TransactionSignature> {
1015
1398
  const { txSig } = await this.txSender.send(
1016
- wrapInTx(
1017
- await this.getCancelOrdersByMarketAndSideIx(
1018
- bestEffort,
1019
- marketIndexOnly,
1020
- directionOnly
1021
- )
1022
- ),
1399
+ wrapInTx(await this.getCancelOrderByUserIdIx(userOrderId)),
1023
1400
  [],
1024
1401
  this.opts
1025
1402
  );
1026
1403
  return txSig;
1027
1404
  }
1028
1405
 
1029
- public async getCancelOrdersByMarketAndSideIx(
1030
- bestEffort?: boolean,
1031
- marketIndexOnly?: BN,
1032
- directionOnly?: PositionDirection
1406
+ public async getCancelOrderByUserIdIx(
1407
+ userOrderId: number
1033
1408
  ): Promise<TransactionInstruction> {
1034
1409
  const userAccountPublicKey = await this.getUserAccountPublicKey();
1035
1410
 
1036
- const remainingAccounts = this.getRemainingAccounts({});
1411
+ const order = this.getOrderByUserId(userOrderId);
1412
+ const oracle = this.getMarketAccount(order.marketIndex).amm.oracle;
1037
1413
 
1038
- for (const order of this.getUserAccount().orders) {
1039
- const oracle = this.getMarketAccount(order.marketIndex).amm.oracle;
1040
- remainingAccounts.push({
1041
- pubkey: oracle,
1042
- isWritable: false,
1043
- isSigner: false,
1044
- });
1045
- }
1414
+ const remainingAccounts = this.getRemainingAccounts({});
1046
1415
 
1047
- return await this.program.instruction.cancelOrdersByMarketAndSide(
1048
- bestEffort,
1049
- marketIndexOnly,
1050
- directionOnly,
1051
- {
1052
- accounts: {
1053
- state: await this.getStatePublicKey(),
1054
- user: userAccountPublicKey,
1055
- authority: this.wallet.publicKey,
1056
- },
1057
- remainingAccounts,
1058
- }
1059
- );
1416
+ return await this.program.instruction.cancelOrderByUserId(userOrderId, {
1417
+ accounts: {
1418
+ state: await this.getStatePublicKey(),
1419
+ user: userAccountPublicKey,
1420
+ authority: this.wallet.publicKey,
1421
+ oracle,
1422
+ },
1423
+ remainingAccounts,
1424
+ });
1060
1425
  }
1061
1426
 
1062
1427
  public async fillOrder(
1063
1428
  userAccountPublicKey: PublicKey,
1064
1429
  user: UserAccount,
1065
1430
  order?: Order,
1066
- makerInfo?: MakerInfo
1431
+ makerInfo?: MakerInfo,
1432
+ referrerInfo?: ReferrerInfo
1067
1433
  ): Promise<TransactionSignature> {
1068
1434
  const { txSig } = await this.txSender.send(
1069
1435
  wrapInTx(
1070
- await this.getFillOrderIx(userAccountPublicKey, user, order, makerInfo)
1436
+ await this.getFillOrderIx(
1437
+ userAccountPublicKey,
1438
+ user,
1439
+ order,
1440
+ makerInfo,
1441
+ referrerInfo
1442
+ )
1071
1443
  ),
1072
1444
  [],
1073
1445
  this.opts
@@ -1079,56 +1451,82 @@ export class ClearingHouse {
1079
1451
  userAccountPublicKey: PublicKey,
1080
1452
  userAccount: UserAccount,
1081
1453
  order: Order,
1082
- makerInfo?: MakerInfo
1454
+ makerInfo?: MakerInfo,
1455
+ referrerInfo?: ReferrerInfo
1083
1456
  ): Promise<TransactionInstruction> {
1457
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
1458
+ this.program.programId,
1459
+ userAccount.authority
1460
+ );
1461
+
1084
1462
  const fillerPublicKey = await this.getUserAccountPublicKey();
1463
+ const fillerStatsPublicKey = this.getUserStatsAccountPublicKey();
1085
1464
 
1086
- const marketIndex = order.marketIndex;
1465
+ const marketIndex = order
1466
+ ? order.marketIndex
1467
+ : userAccount.orders.find((order) =>
1468
+ order.orderId.eq(userAccount.nextOrderId.sub(ONE))
1469
+ ).marketIndex;
1087
1470
  const marketAccount = this.getMarketAccount(marketIndex);
1088
- const oracle = marketAccount.amm.oracle;
1089
1471
 
1090
- const bankAccountInfos = [
1091
- {
1092
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1093
- isSigner: false,
1094
- isWritable: true,
1095
- },
1096
- ];
1097
- const marketAccountInfos = [
1098
- {
1099
- pubkey: marketAccount.pubkey,
1100
- isWritable: true,
1101
- isSigner: false,
1102
- },
1103
- ];
1104
- const oracleAccountInfos = [
1105
- {
1106
- pubkey: marketAccount.amm.oracle,
1107
- isWritable: false,
1108
- isSigner: false,
1109
- },
1110
- ];
1472
+ const oracleAccountMap = new Map<string, AccountMeta>();
1473
+ const bankAccountMap = new Map<number, AccountMeta>();
1474
+ const marketAccountMap = new Map<number, AccountMeta>();
1475
+
1476
+ for (const bankBalance of userAccount.bankBalances) {
1477
+ if (!bankBalance.balance.eq(ZERO)) {
1478
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
1479
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1480
+ pubkey: bankAccount.pubkey,
1481
+ isSigner: false,
1482
+ isWritable: false,
1483
+ });
1484
+
1485
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
1486
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
1487
+ pubkey: bankAccount.oracle,
1488
+ isSigner: false,
1489
+ isWritable: false,
1490
+ });
1491
+ }
1492
+ }
1493
+ }
1494
+
1111
1495
  for (const position of userAccount.positions) {
1112
1496
  if (
1113
1497
  !positionIsAvailable(position) &&
1114
1498
  !position.marketIndex.eq(order.marketIndex)
1115
1499
  ) {
1116
1500
  const market = this.getMarketAccount(position.marketIndex);
1117
- marketAccountInfos.push({
1501
+ marketAccountMap.set(position.marketIndex.toNumber(), {
1118
1502
  pubkey: market.pubkey,
1119
1503
  isWritable: false,
1120
1504
  isSigner: false,
1121
1505
  });
1122
- oracleAccountInfos.push({
1506
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1123
1507
  pubkey: market.amm.oracle,
1124
1508
  isWritable: false,
1125
1509
  isSigner: false,
1126
1510
  });
1127
1511
  }
1128
1512
  }
1129
- const remainingAccounts = oracleAccountInfos.concat(
1130
- bankAccountInfos.concat(marketAccountInfos)
1131
- );
1513
+
1514
+ marketAccountMap.set(marketIndex.toNumber(), {
1515
+ pubkey: marketAccount.pubkey,
1516
+ isWritable: true,
1517
+ isSigner: false,
1518
+ });
1519
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1520
+ pubkey: marketAccount.amm.oracle,
1521
+ isWritable: false,
1522
+ isSigner: false,
1523
+ });
1524
+
1525
+ const remainingAccounts = [
1526
+ ...oracleAccountMap.values(),
1527
+ ...bankAccountMap.values(),
1528
+ ...marketAccountMap.values(),
1529
+ ];
1132
1530
 
1133
1531
  if (makerInfo) {
1134
1532
  remainingAccounts.push({
@@ -1136,17 +1534,37 @@ export class ClearingHouse {
1136
1534
  isWritable: true,
1137
1535
  isSigner: false,
1138
1536
  });
1537
+ remainingAccounts.push({
1538
+ pubkey: makerInfo.makerStats,
1539
+ isWritable: true,
1540
+ isSigner: false,
1541
+ });
1542
+ }
1543
+
1544
+ if (referrerInfo) {
1545
+ remainingAccounts.push({
1546
+ pubkey: referrerInfo.referrer,
1547
+ isWritable: true,
1548
+ isSigner: false,
1549
+ });
1550
+ remainingAccounts.push({
1551
+ pubkey: referrerInfo.referrerStats,
1552
+ isWritable: true,
1553
+ isSigner: false,
1554
+ });
1139
1555
  }
1140
1556
 
1141
1557
  const orderId = order.orderId;
1142
1558
  const makerOrderId = makerInfo ? makerInfo.order.orderId : null;
1559
+
1143
1560
  return await this.program.instruction.fillOrder(orderId, makerOrderId, {
1144
1561
  accounts: {
1145
1562
  state: await this.getStatePublicKey(),
1146
1563
  filler: fillerPublicKey,
1564
+ fillerStats: fillerStatsPublicKey,
1147
1565
  user: userAccountPublicKey,
1566
+ userStats: userStatsPublicKey,
1148
1567
  authority: this.wallet.publicKey,
1149
- oracle: oracle,
1150
1568
  },
1151
1569
  remainingAccounts,
1152
1570
  });
@@ -1175,55 +1593,71 @@ export class ClearingHouse {
1175
1593
  const marketIndex = order.marketIndex;
1176
1594
  const marketAccount = this.getMarketAccount(marketIndex);
1177
1595
 
1178
- const bankAccountInfos = [
1179
- {
1180
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1181
- isSigner: false,
1182
- isWritable: true,
1183
- },
1184
- ];
1185
- const marketAccountInfos = [
1186
- {
1187
- pubkey: marketAccount.pubkey,
1188
- isWritable: true,
1189
- isSigner: false,
1190
- },
1191
- ];
1192
- const oracleAccountInfos = [
1193
- {
1194
- pubkey: marketAccount.amm.oracle,
1195
- isWritable: false,
1196
- isSigner: false,
1197
- },
1198
- ];
1596
+ const oracleAccountMap = new Map<string, AccountMeta>();
1597
+ const bankAccountMap = new Map<number, AccountMeta>();
1598
+ const marketAccountMap = new Map<number, AccountMeta>();
1599
+
1600
+ for (const bankBalance of userAccount.bankBalances) {
1601
+ if (!bankBalance.balance.eq(ZERO)) {
1602
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
1603
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1604
+ pubkey: bankAccount.pubkey,
1605
+ isSigner: false,
1606
+ isWritable: false,
1607
+ });
1608
+
1609
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
1610
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
1611
+ pubkey: bankAccount.oracle,
1612
+ isSigner: false,
1613
+ isWritable: false,
1614
+ });
1615
+ }
1616
+ }
1617
+ }
1618
+
1199
1619
  for (const position of userAccount.positions) {
1200
1620
  if (
1201
1621
  !positionIsAvailable(position) &&
1202
1622
  !position.marketIndex.eq(order.marketIndex)
1203
1623
  ) {
1204
1624
  const market = this.getMarketAccount(position.marketIndex);
1205
- marketAccountInfos.push({
1625
+ marketAccountMap.set(position.marketIndex.toNumber(), {
1206
1626
  pubkey: market.pubkey,
1207
1627
  isWritable: false,
1208
1628
  isSigner: false,
1209
1629
  });
1210
- oracleAccountInfos.push({
1630
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1211
1631
  pubkey: market.amm.oracle,
1212
1632
  isWritable: false,
1213
1633
  isSigner: false,
1214
1634
  });
1215
1635
  }
1216
1636
  }
1217
- const remainingAccounts = oracleAccountInfos.concat(
1218
- bankAccountInfos.concat(marketAccountInfos)
1219
- );
1220
1637
 
1221
- const orderId = order.orderId;
1222
- return await this.program.instruction.triggerOrder(orderId, {
1223
- accounts: {
1224
- state: await this.getStatePublicKey(),
1225
- filler: fillerPublicKey,
1226
- user: userAccountPublicKey,
1638
+ marketAccountMap.set(marketIndex.toNumber(), {
1639
+ pubkey: marketAccount.pubkey,
1640
+ isWritable: true,
1641
+ isSigner: false,
1642
+ });
1643
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1644
+ pubkey: marketAccount.amm.oracle,
1645
+ isWritable: false,
1646
+ isSigner: false,
1647
+ });
1648
+
1649
+ const remainingAccounts = [
1650
+ ...oracleAccountMap.values(),
1651
+ ...bankAccountMap.values(),
1652
+ ...marketAccountMap.values(),
1653
+ ];
1654
+
1655
+ const orderId = order.orderId;
1656
+ return await this.program.instruction.triggerOrder(orderId, {
1657
+ accounts: {
1658
+ state: await this.getStatePublicKey(),
1659
+ filler: fillerPublicKey,
1660
+ user: userAccountPublicKey,
1227
1661
  authority: this.wallet.publicKey,
1228
1662
  },
1229
1663
  remainingAccounts,
@@ -1231,11 +1665,14 @@ export class ClearingHouse {
1231
1665
  }
1232
1666
 
1233
1667
  public async placeAndTake(
1234
- orderParams: OrderParams,
1235
- makerInfo?: MakerInfo
1668
+ orderParams: OptionalOrderParams,
1669
+ makerInfo?: MakerInfo,
1670
+ referrerInfo?: ReferrerInfo
1236
1671
  ): Promise<TransactionSignature> {
1237
1672
  const { txSig, slot } = await this.txSender.send(
1238
- wrapInTx(await this.getPlaceAndTakeIx(orderParams, makerInfo)),
1673
+ wrapInTx(
1674
+ await this.getPlaceAndTakeIx(orderParams, makerInfo, referrerInfo)
1675
+ ),
1239
1676
  [],
1240
1677
  this.opts
1241
1678
  );
@@ -1244,14 +1681,14 @@ export class ClearingHouse {
1244
1681
  }
1245
1682
 
1246
1683
  public async getPlaceAndTakeIx(
1247
- orderParams: OrderParams,
1248
- makerInfo?: MakerInfo
1684
+ orderParams: OptionalOrderParams,
1685
+ makerInfo?: MakerInfo,
1686
+ referrerInfo?: ReferrerInfo
1249
1687
  ): Promise<TransactionInstruction> {
1688
+ orderParams = this.getOrderParams(orderParams);
1689
+ const userStatsPublicKey = await this.getUserStatsAccountPublicKey();
1250
1690
  const userAccountPublicKey = await this.getUserAccountPublicKey();
1251
1691
 
1252
- const priceOracle = this.getMarketAccount(orderParams.marketIndex).amm
1253
- .oracle;
1254
-
1255
1692
  const remainingAccounts = this.getRemainingAccounts({
1256
1693
  writableMarketIndex: orderParams.marketIndex,
1257
1694
  writableBankIndex: QUOTE_ASSET_BANK_INDEX,
@@ -1265,6 +1702,24 @@ export class ClearingHouse {
1265
1702
  isSigner: false,
1266
1703
  isWritable: true,
1267
1704
  });
1705
+ remainingAccounts.push({
1706
+ pubkey: makerInfo.makerStats,
1707
+ isSigner: false,
1708
+ isWritable: true,
1709
+ });
1710
+ }
1711
+
1712
+ if (referrerInfo) {
1713
+ remainingAccounts.push({
1714
+ pubkey: referrerInfo.referrer,
1715
+ isWritable: true,
1716
+ isSigner: false,
1717
+ });
1718
+ remainingAccounts.push({
1719
+ pubkey: referrerInfo.referrerStats,
1720
+ isWritable: true,
1721
+ isSigner: false,
1722
+ });
1268
1723
  }
1269
1724
 
1270
1725
  return await this.program.instruction.placeAndTake(
@@ -1274,8 +1729,70 @@ export class ClearingHouse {
1274
1729
  accounts: {
1275
1730
  state: await this.getStatePublicKey(),
1276
1731
  user: userAccountPublicKey,
1732
+ userStats: userStatsPublicKey,
1733
+ authority: this.wallet.publicKey,
1734
+ },
1735
+ remainingAccounts,
1736
+ }
1737
+ );
1738
+ }
1739
+
1740
+ public async placeAndMake(
1741
+ orderParams: OptionalOrderParams,
1742
+ takerInfo: TakerInfo,
1743
+ referrerInfo?: ReferrerInfo
1744
+ ): Promise<TransactionSignature> {
1745
+ const { txSig, slot } = await this.txSender.send(
1746
+ wrapInTx(
1747
+ await this.getPlaceAndMakeIx(orderParams, takerInfo, referrerInfo)
1748
+ ),
1749
+ [],
1750
+ this.opts
1751
+ );
1752
+
1753
+ this.marketLastSlotCache.set(orderParams.marketIndex.toNumber(), slot);
1754
+
1755
+ return txSig;
1756
+ }
1757
+
1758
+ public async getPlaceAndMakeIx(
1759
+ orderParams: OptionalOrderParams,
1760
+ takerInfo: TakerInfo,
1761
+ referrerInfo?: ReferrerInfo
1762
+ ): Promise<TransactionInstruction> {
1763
+ orderParams = this.getOrderParams(orderParams);
1764
+ const userStatsPublicKey = this.getUserStatsAccountPublicKey();
1765
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1766
+
1767
+ const remainingAccounts = this.getRemainingAccounts({
1768
+ writableMarketIndex: orderParams.marketIndex,
1769
+ });
1770
+
1771
+ if (referrerInfo) {
1772
+ remainingAccounts.push({
1773
+ pubkey: referrerInfo.referrer,
1774
+ isWritable: true,
1775
+ isSigner: false,
1776
+ });
1777
+ remainingAccounts.push({
1778
+ pubkey: referrerInfo.referrerStats,
1779
+ isWritable: true,
1780
+ isSigner: false,
1781
+ });
1782
+ }
1783
+
1784
+ const takerOrderId = takerInfo!.order!.orderId;
1785
+ return await this.program.instruction.placeAndMake(
1786
+ orderParams,
1787
+ takerOrderId,
1788
+ {
1789
+ accounts: {
1790
+ state: await this.getStatePublicKey(),
1791
+ user: userAccountPublicKey,
1792
+ userStats: userStatsPublicKey,
1793
+ taker: takerInfo.taker,
1794
+ takerStats: takerInfo.takerStats,
1277
1795
  authority: this.wallet.publicKey,
1278
- oracle: priceOracle,
1279
1796
  },
1280
1797
  remainingAccounts,
1281
1798
  }
@@ -1287,22 +1804,23 @@ export class ClearingHouse {
1287
1804
  * @param marketIndex
1288
1805
  * @returns
1289
1806
  */
1290
- public async closePosition(marketIndex: BN): Promise<TransactionSignature> {
1807
+ public async closePosition(
1808
+ marketIndex: BN,
1809
+ limitPrice?: BN
1810
+ ): Promise<TransactionSignature> {
1291
1811
  const userPosition = this.getUser().getUserPosition(marketIndex);
1292
1812
  if (!userPosition) {
1293
1813
  throw Error(`No position in market ${marketIndex.toString()}`);
1294
1814
  }
1295
1815
 
1296
- return await this.placeAndTake(
1297
- getMarketOrderParams(
1298
- marketIndex,
1299
- findDirectionToClose(userPosition),
1300
- ZERO,
1301
- userPosition.baseAssetAmount,
1302
- true,
1303
- undefined
1304
- )
1305
- );
1816
+ return await this.placeAndTake({
1817
+ orderType: OrderType.MARKET,
1818
+ marketIndex,
1819
+ direction: findDirectionToClose(userPosition),
1820
+ baseAssetAmount: userPosition.baseAssetAmount.abs(),
1821
+ reduceOnly: true,
1822
+ price: limitPrice,
1823
+ });
1306
1824
  }
1307
1825
 
1308
1826
  public async settlePNLs(
@@ -1323,7 +1841,14 @@ export class ClearingHouse {
1323
1841
  );
1324
1842
  }
1325
1843
 
1326
- const tx = new Transaction().add(...ixs);
1844
+ const tx = new Transaction()
1845
+ .add(
1846
+ ComputeBudgetProgram.requestUnits({
1847
+ units: 1_000_000,
1848
+ additionalFee: 0,
1849
+ })
1850
+ )
1851
+ .add(...ixs);
1327
1852
 
1328
1853
  const { txSig } = await this.txSender.send(tx, [], this.opts);
1329
1854
  return txSig;
@@ -1356,12 +1881,13 @@ export class ClearingHouse {
1356
1881
  const marketAccountMap = new Map<number, AccountMeta>();
1357
1882
  const oracleAccountMap = new Map<string, AccountMeta>();
1358
1883
  const bankAccountMap = new Map<number, AccountMeta>();
1884
+
1359
1885
  for (const position of settleeUserAccount.positions) {
1360
1886
  if (!positionIsAvailable(position)) {
1361
1887
  const market = this.getMarketAccount(position.marketIndex);
1362
1888
  marketAccountMap.set(position.marketIndex.toNumber(), {
1363
1889
  pubkey: market.pubkey,
1364
- isWritable: true, // TODO
1890
+ isWritable: false,
1365
1891
  isSigner: false,
1366
1892
  });
1367
1893
  oracleAccountMap.set(market.amm.oracle.toString(), {
@@ -1424,72 +1950,451 @@ export class ClearingHouse {
1424
1950
  });
1425
1951
  }
1426
1952
 
1427
- public async liquidate(
1428
- liquidateeUserAccountPublicKey: PublicKey
1953
+ public async liquidatePerp(
1954
+ userAccountPublicKey: PublicKey,
1955
+ userAccount: UserAccount,
1956
+ marketIndex: BN,
1957
+ maxBaseAssetAmount: BN
1958
+ ): Promise<TransactionSignature> {
1959
+ const { txSig } = await this.txSender.send(
1960
+ wrapInTx(
1961
+ await this.getLiquidatePerpIx(
1962
+ userAccountPublicKey,
1963
+ userAccount,
1964
+ marketIndex,
1965
+ maxBaseAssetAmount
1966
+ )
1967
+ ),
1968
+ [],
1969
+ this.opts
1970
+ );
1971
+ return txSig;
1972
+ }
1973
+
1974
+ public async getLiquidatePerpIx(
1975
+ userAccountPublicKey: PublicKey,
1976
+ userAccount: UserAccount,
1977
+ marketIndex: BN,
1978
+ maxBaseAssetAmount: BN
1979
+ ): Promise<TransactionInstruction> {
1980
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
1981
+ this.program.programId,
1982
+ userAccount.authority
1983
+ );
1984
+
1985
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
1986
+ const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey();
1987
+
1988
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
1989
+ writableMarketIndex: marketIndex,
1990
+ userAccount,
1991
+ });
1992
+
1993
+ return await this.program.instruction.liquidatePerp(
1994
+ marketIndex,
1995
+ maxBaseAssetAmount,
1996
+ {
1997
+ accounts: {
1998
+ state: await this.getStatePublicKey(),
1999
+ authority: this.wallet.publicKey,
2000
+ user: userAccountPublicKey,
2001
+ userStats: userStatsPublicKey,
2002
+ liquidator: liquidatorPublicKey,
2003
+ liquidatorStats: liquidatorStatsPublicKey,
2004
+ },
2005
+ remainingAccounts: remainingAccounts,
2006
+ }
2007
+ );
2008
+ }
2009
+
2010
+ public async liquidateBorrow(
2011
+ userAccountPublicKey: PublicKey,
2012
+ userAccount: UserAccount,
2013
+ assetBankIndex: BN,
2014
+ liabilityBankIndex: BN,
2015
+ maxLiabilityTransfer: BN
1429
2016
  ): Promise<TransactionSignature> {
1430
2017
  const { txSig } = await this.txSender.send(
1431
- wrapInTx(await this.getLiquidateIx(liquidateeUserAccountPublicKey)),
2018
+ wrapInTx(
2019
+ await this.getLiquidateBorrowIx(
2020
+ userAccountPublicKey,
2021
+ userAccount,
2022
+ assetBankIndex,
2023
+ liabilityBankIndex,
2024
+ maxLiabilityTransfer
2025
+ )
2026
+ ),
1432
2027
  [],
1433
2028
  this.opts
1434
2029
  );
1435
2030
  return txSig;
1436
2031
  }
1437
2032
 
1438
- public async getLiquidateIx(
1439
- liquidateeUserAccountPublicKey: PublicKey
2033
+ public async getLiquidateBorrowIx(
2034
+ userAccountPublicKey: PublicKey,
2035
+ userAccount: UserAccount,
2036
+ assetBankIndex: BN,
2037
+ liabilityBankIndex: BN,
2038
+ maxLiabilityTransfer: BN
1440
2039
  ): Promise<TransactionInstruction> {
1441
- const userAccountPublicKey = await this.getUserAccountPublicKey();
1442
- const liquidateeUserAccount = (await this.program.account.user.fetch(
1443
- liquidateeUserAccountPublicKey
1444
- )) as UserAccount;
2040
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2041
+
2042
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
2043
+ userAccount,
2044
+ writableBankIndexes: [liabilityBankIndex, assetBankIndex],
2045
+ });
1445
2046
 
1446
- const bankAccountInfos = [
2047
+ return await this.program.instruction.liquidateBorrow(
2048
+ assetBankIndex,
2049
+ liabilityBankIndex,
2050
+ maxLiabilityTransfer,
1447
2051
  {
1448
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1449
- isSigner: false,
1450
- isWritable: true,
2052
+ accounts: {
2053
+ state: await this.getStatePublicKey(),
2054
+ authority: this.wallet.publicKey,
2055
+ user: userAccountPublicKey,
2056
+ liquidator: liquidatorPublicKey,
2057
+ },
2058
+ remainingAccounts: remainingAccounts,
2059
+ }
2060
+ );
2061
+ }
2062
+
2063
+ public async liquidateBorrowForPerpPnl(
2064
+ userAccountPublicKey: PublicKey,
2065
+ userAccount: UserAccount,
2066
+ perpMarketIndex: BN,
2067
+ liabilityBankIndex: BN,
2068
+ maxLiabilityTransfer: BN
2069
+ ): Promise<TransactionSignature> {
2070
+ const { txSig } = await this.txSender.send(
2071
+ wrapInTx(
2072
+ await this.getLiquidateBorrowForPerpPnlIx(
2073
+ userAccountPublicKey,
2074
+ userAccount,
2075
+ perpMarketIndex,
2076
+ liabilityBankIndex,
2077
+ maxLiabilityTransfer
2078
+ )
2079
+ ),
2080
+ [],
2081
+ this.opts
2082
+ );
2083
+ return txSig;
2084
+ }
2085
+
2086
+ public async getLiquidateBorrowForPerpPnlIx(
2087
+ userAccountPublicKey: PublicKey,
2088
+ userAccount: UserAccount,
2089
+ perpMarketIndex: BN,
2090
+ liabilityBankIndex: BN,
2091
+ maxLiabilityTransfer: BN
2092
+ ): Promise<TransactionInstruction> {
2093
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2094
+
2095
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
2096
+ userAccount,
2097
+ writableMarketIndex: perpMarketIndex,
2098
+ writableBankIndexes: [liabilityBankIndex],
2099
+ });
2100
+
2101
+ return await this.program.instruction.liquidateBorrowForPerpPnl(
2102
+ perpMarketIndex,
2103
+ liabilityBankIndex,
2104
+ maxLiabilityTransfer,
2105
+ {
2106
+ accounts: {
2107
+ state: await this.getStatePublicKey(),
2108
+ authority: this.wallet.publicKey,
2109
+ user: userAccountPublicKey,
2110
+ liquidator: liquidatorPublicKey,
2111
+ },
2112
+ remainingAccounts: remainingAccounts,
2113
+ }
2114
+ );
2115
+ }
2116
+
2117
+ public async liquidatePerpPnlForDeposit(
2118
+ userAccountPublicKey: PublicKey,
2119
+ userAccount: UserAccount,
2120
+ perpMarketIndex: BN,
2121
+ assetBankIndex: BN,
2122
+ maxPnlTransfer: BN
2123
+ ): Promise<TransactionSignature> {
2124
+ const { txSig } = await this.txSender.send(
2125
+ wrapInTx(
2126
+ await this.getLiquidatePerpPnlForDepositIx(
2127
+ userAccountPublicKey,
2128
+ userAccount,
2129
+ perpMarketIndex,
2130
+ assetBankIndex,
2131
+ maxPnlTransfer
2132
+ )
2133
+ ),
2134
+ [],
2135
+ this.opts
2136
+ );
2137
+ return txSig;
2138
+ }
2139
+
2140
+ public async getLiquidatePerpPnlForDepositIx(
2141
+ userAccountPublicKey: PublicKey,
2142
+ userAccount: UserAccount,
2143
+ perpMarketIndex: BN,
2144
+ assetBankIndex: BN,
2145
+ maxPnlTransfer: BN
2146
+ ): Promise<TransactionInstruction> {
2147
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2148
+
2149
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
2150
+ userAccount,
2151
+ writableMarketIndex: perpMarketIndex,
2152
+ writableBankIndexes: [assetBankIndex],
2153
+ });
2154
+
2155
+ return await this.program.instruction.liquidatePerpPnlForDeposit(
2156
+ perpMarketIndex,
2157
+ assetBankIndex,
2158
+ maxPnlTransfer,
2159
+ {
2160
+ accounts: {
2161
+ state: await this.getStatePublicKey(),
2162
+ authority: this.wallet.publicKey,
2163
+ user: userAccountPublicKey,
2164
+ liquidator: liquidatorPublicKey,
2165
+ },
2166
+ remainingAccounts: remainingAccounts,
2167
+ }
2168
+ );
2169
+ }
2170
+
2171
+ public async resolvePerpBankruptcy(
2172
+ userAccountPublicKey: PublicKey,
2173
+ userAccount: UserAccount,
2174
+ marketIndex: BN
2175
+ ): Promise<TransactionSignature> {
2176
+ const { txSig } = await this.txSender.send(
2177
+ wrapInTx(
2178
+ await this.getResolvePerpBankruptcyIx(
2179
+ userAccountPublicKey,
2180
+ userAccount,
2181
+ marketIndex
2182
+ )
2183
+ ),
2184
+ [],
2185
+ this.opts
2186
+ );
2187
+ return txSig;
2188
+ }
2189
+
2190
+ public async getResolvePerpBankruptcyIx(
2191
+ userAccountPublicKey: PublicKey,
2192
+ userAccount: UserAccount,
2193
+ marketIndex: BN
2194
+ ): Promise<TransactionInstruction> {
2195
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2196
+
2197
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
2198
+ writableMarketIndex: marketIndex,
2199
+ userAccount,
2200
+ });
2201
+
2202
+ return await this.program.instruction.resolvePerpBankruptcy(marketIndex, {
2203
+ accounts: {
2204
+ state: await this.getStatePublicKey(),
2205
+ authority: this.wallet.publicKey,
2206
+ user: userAccountPublicKey,
2207
+ liquidator: liquidatorPublicKey,
1451
2208
  },
1452
- ];
1453
- const marketAccountInfos = [];
1454
- const oracleAccountInfos = [];
2209
+ remainingAccounts: remainingAccounts,
2210
+ });
2211
+ }
2212
+
2213
+ public async resolveBorrowBankruptcy(
2214
+ userAccountPublicKey: PublicKey,
2215
+ userAccount: UserAccount,
2216
+ bankIndex: BN
2217
+ ): Promise<TransactionSignature> {
2218
+ const { txSig } = await this.txSender.send(
2219
+ wrapInTx(
2220
+ await this.getResolveBorrowBankruptcyIx(
2221
+ userAccountPublicKey,
2222
+ userAccount,
2223
+ bankIndex
2224
+ )
2225
+ ),
2226
+ [],
2227
+ this.opts
2228
+ );
2229
+ return txSig;
2230
+ }
2231
+
2232
+ public async getResolveBorrowBankruptcyIx(
2233
+ userAccountPublicKey: PublicKey,
2234
+ userAccount: UserAccount,
2235
+ bankIndex: BN
2236
+ ): Promise<TransactionInstruction> {
2237
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2238
+
2239
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
2240
+ writableBankIndexes: [bankIndex],
2241
+ userAccount,
2242
+ });
2243
+
2244
+ return await this.program.instruction.resolveBorrowBankruptcy(bankIndex, {
2245
+ accounts: {
2246
+ state: await this.getStatePublicKey(),
2247
+ authority: this.wallet.publicKey,
2248
+ user: userAccountPublicKey,
2249
+ liquidator: liquidatorPublicKey,
2250
+ },
2251
+ remainingAccounts: remainingAccounts,
2252
+ });
2253
+ }
2254
+
2255
+ getRemainingAccountsForLiquidation(params: {
2256
+ userAccount: UserAccount;
2257
+ writableMarketIndex?: BN;
2258
+ writableBankIndexes?: BN[];
2259
+ }): AccountMeta[] {
2260
+ const liquidateeUserAccount = params.userAccount;
2261
+
2262
+ const oracleAccountMap = new Map<string, AccountMeta>();
2263
+ const bankAccountMap = new Map<number, AccountMeta>();
2264
+ const marketAccountMap = new Map<number, AccountMeta>();
2265
+ for (const bankBalance of liquidateeUserAccount.bankBalances) {
2266
+ if (!bankBalance.balance.eq(ZERO)) {
2267
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
2268
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
2269
+ pubkey: bankAccount.pubkey,
2270
+ isSigner: false,
2271
+ isWritable: false,
2272
+ });
2273
+
2274
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
2275
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
2276
+ pubkey: bankAccount.oracle,
2277
+ isSigner: false,
2278
+ isWritable: false,
2279
+ });
2280
+ }
2281
+ }
2282
+ }
1455
2283
  for (const position of liquidateeUserAccount.positions) {
1456
2284
  if (!positionIsAvailable(position)) {
1457
2285
  const market = this.getMarketAccount(position.marketIndex);
1458
- const marketPublicKey = await getMarketPublicKey(
1459
- this.program.programId,
1460
- position.marketIndex
1461
- );
1462
- marketAccountInfos.push({
1463
- pubkey: marketPublicKey,
1464
- isWritable: true,
2286
+ marketAccountMap.set(position.marketIndex.toNumber(), {
2287
+ pubkey: market.pubkey,
2288
+ isWritable: false,
1465
2289
  isSigner: false,
1466
2290
  });
1467
- oracleAccountInfos.push({
2291
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1468
2292
  pubkey: market.amm.oracle,
1469
2293
  isWritable: false,
1470
2294
  isSigner: false,
1471
2295
  });
1472
2296
  }
1473
2297
  }
1474
- const remainingAccounts = oracleAccountInfos.concat(
1475
- bankAccountInfos.concat(marketAccountInfos)
1476
- );
1477
2298
 
1478
- const state = this.getStateAccount();
1479
- const quoteAssetBankAccount = this.getQuoteAssetBankAccount();
1480
- return await this.program.instruction.liquidate({
1481
- accounts: {
1482
- state: await this.getStatePublicKey(),
1483
- authority: this.wallet.publicKey,
1484
- user: liquidateeUserAccountPublicKey,
1485
- liquidator: userAccountPublicKey,
1486
- bankVault: quoteAssetBankAccount.vault,
1487
- bankVaultAuthority: quoteAssetBankAccount.vaultAuthority,
1488
- insuranceVault: state.insuranceVault,
1489
- tokenProgram: TOKEN_PROGRAM_ID,
1490
- },
1491
- remainingAccounts: remainingAccounts,
1492
- });
2299
+ const userAccountAndSlot = this.getUserAccountAndSlot();
2300
+ if (!userAccountAndSlot) {
2301
+ throw Error(
2302
+ 'No user account found. Most likely user account does not exist or failed to fetch account'
2303
+ );
2304
+ }
2305
+ const { data: userAccount, slot: lastUserPositionsSlot } =
2306
+ userAccountAndSlot;
2307
+
2308
+ for (const [marketIndexNum, slot] of this.marketLastSlotCache.entries()) {
2309
+ // if cache has more recent slot than user positions account slot, add market to remaining accounts
2310
+ // otherwise remove from slot
2311
+ if (slot > lastUserPositionsSlot) {
2312
+ const marketAccount = this.getMarketAccount(marketIndexNum);
2313
+ marketAccountMap.set(marketIndexNum, {
2314
+ pubkey: marketAccount.pubkey,
2315
+ isSigner: false,
2316
+ isWritable: false,
2317
+ });
2318
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
2319
+ pubkey: marketAccount.amm.oracle,
2320
+ isSigner: false,
2321
+ isWritable: false,
2322
+ });
2323
+ } else {
2324
+ this.marketLastSlotCache.delete(marketIndexNum);
2325
+ }
2326
+ }
2327
+ for (const bankBalance of userAccount.bankBalances) {
2328
+ if (!bankBalance.balance.eq(ZERO)) {
2329
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
2330
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
2331
+ pubkey: bankAccount.pubkey,
2332
+ isSigner: false,
2333
+ isWritable: false,
2334
+ });
2335
+
2336
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
2337
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
2338
+ pubkey: bankAccount.oracle,
2339
+ isSigner: false,
2340
+ isWritable: false,
2341
+ });
2342
+ }
2343
+ }
2344
+ }
2345
+ for (const position of userAccount.positions) {
2346
+ if (!positionIsAvailable(position)) {
2347
+ const market = this.getMarketAccount(position.marketIndex);
2348
+ marketAccountMap.set(position.marketIndex.toNumber(), {
2349
+ pubkey: market.pubkey,
2350
+ isWritable: false,
2351
+ isSigner: false,
2352
+ });
2353
+ oracleAccountMap.set(market.amm.oracle.toString(), {
2354
+ pubkey: market.amm.oracle,
2355
+ isWritable: false,
2356
+ isSigner: false,
2357
+ });
2358
+ }
2359
+ }
2360
+
2361
+ if (params.writableMarketIndex) {
2362
+ const market = this.getMarketAccount(params.writableMarketIndex);
2363
+ marketAccountMap.set(market.marketIndex.toNumber(), {
2364
+ pubkey: market.pubkey,
2365
+ isSigner: false,
2366
+ isWritable: true,
2367
+ });
2368
+ oracleAccountMap.set(market.amm.oracle.toString(), {
2369
+ pubkey: market.amm.oracle,
2370
+ isSigner: false,
2371
+ isWritable: false,
2372
+ });
2373
+ }
2374
+
2375
+ if (params.writableBankIndexes) {
2376
+ for (const writableBankIndex of params.writableBankIndexes) {
2377
+ const bank = this.getBankAccount(writableBankIndex);
2378
+ bankAccountMap.set(bank.bankIndex.toNumber(), {
2379
+ pubkey: bank.pubkey,
2380
+ isSigner: false,
2381
+ isWritable: true,
2382
+ });
2383
+ if (!bank.oracle.equals(PublicKey.default)) {
2384
+ oracleAccountMap.set(bank.oracle.toString(), {
2385
+ pubkey: bank.oracle,
2386
+ isSigner: false,
2387
+ isWritable: false,
2388
+ });
2389
+ }
2390
+ }
2391
+ }
2392
+
2393
+ return [
2394
+ ...oracleAccountMap.values(),
2395
+ ...bankAccountMap.values(),
2396
+ ...marketAccountMap.values(),
2397
+ ];
1493
2398
  }
1494
2399
 
1495
2400
  public async updateFundingRate(