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

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