@drift-labs/sdk 0.2.0-master.10 → 0.2.0-master.13

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 (82) hide show
  1. package/lib/admin.d.ts +3 -3
  2. package/lib/admin.js +6 -6
  3. package/lib/clearingHouse.d.ts +22 -6
  4. package/lib/clearingHouse.js +357 -74
  5. package/lib/clearingHouseUser.d.ts +12 -17
  6. package/lib/clearingHouseUser.js +97 -88
  7. package/lib/config.js +1 -1
  8. package/lib/constants/banks.d.ts +2 -2
  9. package/lib/constants/banks.js +5 -4
  10. package/lib/constants/numericConstants.d.ts +3 -0
  11. package/lib/constants/numericConstants.js +4 -1
  12. package/lib/events/eventList.js +3 -0
  13. package/lib/events/types.d.ts +2 -1
  14. package/lib/factory/bigNum.d.ts +1 -0
  15. package/lib/factory/bigNum.js +37 -11
  16. package/lib/idl/clearing_house.json +611 -146
  17. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  18. package/lib/index.d.ts +2 -1
  19. package/lib/index.js +2 -1
  20. package/lib/math/amm.js +2 -2
  21. package/lib/math/bankBalance.d.ts +3 -1
  22. package/lib/math/bankBalance.js +54 -1
  23. package/lib/math/margin.d.ts +11 -0
  24. package/lib/math/margin.js +72 -0
  25. package/lib/math/market.d.ts +4 -1
  26. package/lib/math/market.js +35 -1
  27. package/lib/math/position.d.ts +8 -0
  28. package/lib/math/position.js +42 -12
  29. package/lib/orders.d.ts +1 -2
  30. package/lib/orders.js +2 -77
  31. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  32. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  33. package/lib/tx/retryTxSender.js +9 -2
  34. package/lib/types.d.ts +86 -16
  35. package/lib/types.js +18 -1
  36. package/lib/util/computeUnits.js +1 -1
  37. package/lib/util/getTokenAddress.d.ts +2 -0
  38. package/lib/util/getTokenAddress.js +9 -0
  39. package/package.json +1 -1
  40. package/src/admin.ts +7 -7
  41. package/src/clearingHouse.ts +636 -94
  42. package/src/clearingHouseConfig.js +2 -0
  43. package/src/clearingHouseUser.ts +219 -121
  44. package/src/clearingHouseUserConfig.js +2 -0
  45. package/src/config.ts +1 -1
  46. package/src/constants/banks.js +42 -0
  47. package/src/constants/banks.ts +7 -4
  48. package/src/constants/markets.js +42 -0
  49. package/src/constants/numericConstants.js +41 -0
  50. package/src/constants/numericConstants.ts +4 -0
  51. package/src/events/eventList.ts +3 -0
  52. package/src/events/types.ts +2 -0
  53. package/src/factory/bigNum.js +37 -11
  54. package/src/factory/bigNum.ts +43 -13
  55. package/src/idl/clearing_house.json +611 -146
  56. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  57. package/src/index.js +1 -1
  58. package/src/index.ts +2 -1
  59. package/src/math/amm.ts +8 -5
  60. package/src/math/bankBalance.ts +98 -1
  61. package/src/math/margin.ts +124 -0
  62. package/src/math/market.ts +66 -1
  63. package/src/math/position.ts +59 -9
  64. package/src/mockUSDCFaucet.js +276 -167
  65. package/src/orders.ts +4 -157
  66. package/src/tokenFaucet.js +189 -0
  67. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  68. package/src/tx/retryTxSender.ts +11 -3
  69. package/src/types.js +12 -1
  70. package/src/types.ts +88 -16
  71. package/src/{accounts/fetch.js → util/computeUnits.js} +11 -13
  72. package/src/util/computeUnits.ts +1 -1
  73. package/src/util/getTokenAddress.js +9 -0
  74. package/src/util/getTokenAddress.ts +18 -0
  75. package/tests/bn/test.ts +2 -0
  76. package/src/addresses/pda.js +0 -104
  77. package/src/math/bankBalance.js +0 -75
  78. package/src/math/market.js +0 -57
  79. package/src/math/orders.js +0 -110
  80. package/src/math/position.js +0 -140
  81. package/src/orders.js +0 -134
  82. package/src/tx/retryTxSender.js +0 -188
@@ -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,
@@ -27,9 +27,13 @@ import {
27
27
  Transaction,
28
28
  TransactionInstruction,
29
29
  AccountMeta,
30
+ Keypair,
31
+ LAMPORTS_PER_SOL,
32
+ Signer,
33
+ SystemProgram,
30
34
  } from '@solana/web3.js';
31
35
 
32
- import { MockUSDCFaucet } from './mockUSDCFaucet';
36
+ import { TokenFaucet } from './tokenFaucet';
33
37
  import { EventEmitter } from 'events';
34
38
  import StrictEventEmitter from 'strict-event-emitter-types';
35
39
  import {
@@ -57,6 +61,7 @@ import { RetryTxSender } from './tx/retryTxSender';
57
61
  import { ClearingHouseUser } from './clearingHouseUser';
58
62
  import { ClearingHouseUserAccountSubscriptionConfig } from './clearingHouseUserConfig';
59
63
  import { getMarketsBanksAndOraclesForSubscription } from './config';
64
+ import { WRAPPED_SOL_MINT } from './constants/banks';
60
65
 
61
66
  /**
62
67
  * # ClearingHouse
@@ -399,6 +404,7 @@ export class ClearingHouse {
399
404
  getRemainingAccounts(params: {
400
405
  writableMarketIndex?: BN;
401
406
  writableBankIndex?: BN;
407
+ readableMarketIndex?: BN;
402
408
  }): AccountMeta[] {
403
409
  const userAccountAndSlot = this.getUserAccountAndSlot();
404
410
  if (!userAccountAndSlot) {
@@ -439,8 +445,7 @@ export class ClearingHouse {
439
445
  marketAccountMap.set(marketIndexNum, {
440
446
  pubkey: marketAccount.pubkey,
441
447
  isSigner: false,
442
- // isWritable: false, // TODO
443
- isWritable: true,
448
+ isWritable: false,
444
449
  });
445
450
  oracleAccountMap.set(marketAccount.pubkey.toString(), {
446
451
  pubkey: marketAccount.amm.oracle,
@@ -450,6 +455,22 @@ export class ClearingHouse {
450
455
  }
451
456
  }
452
457
 
458
+ if (params.readableMarketIndex) {
459
+ const marketAccount = this.getMarketAccount(
460
+ params.readableMarketIndex.toNumber()
461
+ );
462
+ marketAccountMap.set(params.readableMarketIndex.toNumber(), {
463
+ pubkey: marketAccount.pubkey,
464
+ isSigner: false,
465
+ isWritable: true,
466
+ });
467
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
468
+ pubkey: marketAccount.amm.oracle,
469
+ isSigner: false,
470
+ isWritable: false,
471
+ });
472
+ }
473
+
453
474
  if (params.writableMarketIndex) {
454
475
  const marketAccount = this.getMarketAccount(
455
476
  params.writableMarketIndex.toNumber()
@@ -527,6 +548,31 @@ export class ClearingHouse {
527
548
  userId?: number,
528
549
  reduceOnly = false
529
550
  ): Promise<TransactionSignature> {
551
+ const tx = new Transaction();
552
+ const additionalSigners: Array<Signer> = [];
553
+
554
+ const bank = this.getBankAccount(bankIndex);
555
+
556
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
557
+
558
+ const authority = this.wallet.publicKey;
559
+
560
+ const createWSOLTokenAccount =
561
+ isSolBank && collateralAccountPublicKey.equals(authority);
562
+
563
+ if (createWSOLTokenAccount) {
564
+ const { ixs, signers, pubkey } =
565
+ await this.getWrappedSolAccountCreationIxs(amount);
566
+
567
+ collateralAccountPublicKey = pubkey;
568
+
569
+ ixs.forEach((ix) => {
570
+ tx.add(ix);
571
+ });
572
+
573
+ signers.forEach((signer) => additionalSigners.push(signer));
574
+ }
575
+
530
576
  const depositCollateralIx = await this.getDepositInstruction(
531
577
  amount,
532
578
  bankIndex,
@@ -536,9 +582,26 @@ export class ClearingHouse {
536
582
  true
537
583
  );
538
584
 
539
- const tx = new Transaction().add(depositCollateralIx);
585
+ tx.add(depositCollateralIx);
586
+
587
+ // Close the wrapped sol account at the end of the transaction
588
+ if (createWSOLTokenAccount) {
589
+ tx.add(
590
+ Token.createCloseAccountInstruction(
591
+ TOKEN_PROGRAM_ID,
592
+ collateralAccountPublicKey,
593
+ authority,
594
+ authority,
595
+ []
596
+ )
597
+ );
598
+ }
540
599
 
541
- const { txSig } = await this.txSender.send(tx);
600
+ const { txSig } = await this.txSender.send(
601
+ tx,
602
+ additionalSigners,
603
+ this.opts
604
+ );
542
605
  return txSig;
543
606
  }
544
607
 
@@ -600,12 +663,119 @@ export class ClearingHouse {
600
663
  );
601
664
  }
602
665
 
666
+ private async checkIfAccountExists(account: PublicKey) {
667
+ try {
668
+ const accountInfo = await this.connection.getAccountInfo(account);
669
+ return accountInfo && true;
670
+ } catch (e) {
671
+ // Doesn't already exist
672
+ return false;
673
+ }
674
+ }
675
+
676
+ private async getSolWithdrawalIxs(
677
+ bankIndex: BN,
678
+ amount: BN
679
+ ): Promise<{
680
+ ixs: anchor.web3.TransactionInstruction[];
681
+ signers: Signer[];
682
+ pubkey: PublicKey;
683
+ }> {
684
+ const result = {
685
+ ixs: [],
686
+ signers: [],
687
+ pubkey: PublicKey.default,
688
+ };
689
+
690
+ // Create a temporary wrapped SOL account to store the SOL that we're withdrawing
691
+
692
+ const authority = this.wallet.publicKey;
693
+
694
+ const { ixs, signers, pubkey } = await this.getWrappedSolAccountCreationIxs(
695
+ amount
696
+ );
697
+ result.pubkey = pubkey;
698
+
699
+ ixs.forEach((ix) => {
700
+ result.ixs.push(ix);
701
+ });
702
+
703
+ signers.forEach((ix) => {
704
+ result.signers.push(ix);
705
+ });
706
+
707
+ const withdrawIx = await this.getWithdrawIx(
708
+ amount,
709
+ bankIndex,
710
+ pubkey,
711
+ true
712
+ );
713
+
714
+ result.ixs.push(withdrawIx);
715
+
716
+ result.ixs.push(
717
+ Token.createCloseAccountInstruction(
718
+ TOKEN_PROGRAM_ID,
719
+ pubkey,
720
+ authority,
721
+ authority,
722
+ []
723
+ )
724
+ );
725
+
726
+ return result;
727
+ }
728
+
729
+ private async getWrappedSolAccountCreationIxs(amount: BN): Promise<{
730
+ ixs: anchor.web3.TransactionInstruction[];
731
+ signers: Signer[];
732
+ pubkey: PublicKey;
733
+ }> {
734
+ const wrappedSolAccount = new Keypair();
735
+
736
+ const result = {
737
+ ixs: [],
738
+ signers: [],
739
+ pubkey: wrappedSolAccount.publicKey,
740
+ };
741
+
742
+ const rentSpaceLamports = new BN(LAMPORTS_PER_SOL / 100);
743
+
744
+ const depositAmountLamports = amount.add(rentSpaceLamports);
745
+
746
+ const authority = this.wallet.publicKey;
747
+
748
+ result.ixs.push(
749
+ SystemProgram.createAccount({
750
+ fromPubkey: authority,
751
+ newAccountPubkey: wrappedSolAccount.publicKey,
752
+ lamports: depositAmountLamports.toNumber(),
753
+ space: 165,
754
+ programId: TOKEN_PROGRAM_ID,
755
+ })
756
+ );
757
+
758
+ result.ixs.push(
759
+ Token.createInitAccountInstruction(
760
+ TOKEN_PROGRAM_ID,
761
+ WRAPPED_SOL_MINT,
762
+ wrappedSolAccount.publicKey,
763
+ authority
764
+ )
765
+ );
766
+
767
+ result.signers.push(wrappedSolAccount);
768
+
769
+ return result;
770
+ }
771
+
603
772
  /**
604
773
  * Creates the Clearing House User account for a user, and deposits some initial collateral
605
- * @param userId
606
- * @param name
607
774
  * @param amount
608
775
  * @param userTokenAccount
776
+ * @param bankIndex
777
+ * @param userId
778
+ * @param name
609
779
  * @param fromUserId
610
780
  * @returns
611
781
  */
@@ -620,6 +790,35 @@ export class ClearingHouse {
620
790
  const [userAccountPublicKey, initializeUserAccountIx] =
621
791
  await this.getInitializeUserInstructions(userId, name);
622
792
 
793
+ const additionalSigners: Array<Signer> = [];
794
+
795
+ const bank = this.getBankAccount(bankIndex);
796
+
797
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
798
+
799
+ const tx = new Transaction();
800
+
801
+ const authority = this.wallet.publicKey;
802
+
803
+ const createWSOLTokenAccount =
804
+ isSolBank && userTokenAccount.equals(authority);
805
+
806
+ if (createWSOLTokenAccount) {
807
+ const {
808
+ ixs: startIxs,
809
+ signers,
810
+ pubkey,
811
+ } = await this.getWrappedSolAccountCreationIxs(amount);
812
+
813
+ userTokenAccount = pubkey;
814
+
815
+ startIxs.forEach((ix) => {
816
+ tx.add(ix);
817
+ });
818
+
819
+ signers.forEach((signer) => additionalSigners.push(signer));
820
+ }
821
+
623
822
  const depositCollateralIx =
624
823
  fromUserId != null
625
824
  ? await this.getTransferDepositIx(amount, bankIndex, fromUserId, userId)
@@ -632,11 +831,26 @@ export class ClearingHouse {
632
831
  false
633
832
  );
634
833
 
635
- const tx = new Transaction()
636
- .add(initializeUserAccountIx)
637
- .add(depositCollateralIx);
834
+ tx.add(initializeUserAccountIx).add(depositCollateralIx);
835
+
836
+ // Close the wrapped sol account at the end of the transaction
837
+ if (createWSOLTokenAccount) {
838
+ tx.add(
839
+ Token.createCloseAccountInstruction(
840
+ TOKEN_PROGRAM_ID,
841
+ userTokenAccount,
842
+ authority,
843
+ authority,
844
+ []
845
+ )
846
+ );
847
+ }
638
848
 
639
- const { txSig } = await this.txSender.send(tx, []);
849
+ const { txSig } = await this.txSender.send(
850
+ tx,
851
+ additionalSigners,
852
+ this.opts
853
+ );
640
854
 
641
855
  return [txSig, userAccountPublicKey];
642
856
  }
@@ -644,11 +858,12 @@ export class ClearingHouse {
644
858
  public async initializeUserAccountForDevnet(
645
859
  userId = 0,
646
860
  name = DEFAULT_USER_NAME,
647
- mockUSDCFaucet: MockUSDCFaucet,
861
+ bankIndex: BN,
862
+ tokenFaucet: TokenFaucet,
648
863
  amount: BN
649
864
  ): Promise<[TransactionSignature, PublicKey]> {
650
865
  const [associateTokenPublicKey, createAssociatedAccountIx, mintToIx] =
651
- await mockUSDCFaucet.createAssociatedTokenAccountAndMintToInstructions(
866
+ await tokenFaucet.createAssociatedTokenAccountAndMintToInstructions(
652
867
  this.wallet.publicKey,
653
868
  amount
654
869
  );
@@ -658,7 +873,7 @@ export class ClearingHouse {
658
873
 
659
874
  const depositCollateralIx = await this.getDepositInstruction(
660
875
  amount,
661
- new BN(0),
876
+ bankIndex,
662
877
  associateTokenPublicKey,
663
878
  userId,
664
879
  false,
@@ -682,16 +897,56 @@ export class ClearingHouse {
682
897
  userTokenAccount: PublicKey,
683
898
  reduceOnly = false
684
899
  ): Promise<TransactionSignature> {
685
- const { txSig } = await this.txSender.send(
686
- wrapInTx(
687
- await this.getWithdrawIx(
688
- amount,
689
- bankIndex,
900
+ const tx = new Transaction();
901
+ const additionalSigners: Array<Signer> = [];
902
+
903
+ const bank = this.getBankAccount(bankIndex);
904
+
905
+ const isSolBank = bank.mint.equals(WRAPPED_SOL_MINT);
906
+
907
+ const authority = this.wallet.publicKey;
908
+
909
+ const createWSOLTokenAccount =
910
+ isSolBank && userTokenAccount.equals(authority);
911
+
912
+ if (createWSOLTokenAccount) {
913
+ const { ixs, signers, pubkey } =
914
+ await this.getWrappedSolAccountCreationIxs(amount);
915
+
916
+ userTokenAccount = pubkey;
917
+
918
+ ixs.forEach((ix) => {
919
+ tx.add(ix);
920
+ });
921
+
922
+ signers.forEach((signer) => additionalSigners.push(signer));
923
+ }
924
+
925
+ const withdrawCollateral = await this.getWithdrawIx(
926
+ amount,
927
+ bank.bankIndex,
928
+ userTokenAccount,
929
+ reduceOnly
930
+ );
931
+
932
+ tx.add(withdrawCollateral);
933
+
934
+ // Close the wrapped sol account at the end of the transaction
935
+ if (createWSOLTokenAccount) {
936
+ tx.add(
937
+ Token.createCloseAccountInstruction(
938
+ TOKEN_PROGRAM_ID,
690
939
  userTokenAccount,
691
- reduceOnly
940
+ authority,
941
+ authority,
942
+ []
692
943
  )
693
- ),
694
- [],
944
+ );
945
+ }
946
+
947
+ const { txSig } = await this.txSender.send(
948
+ tx,
949
+ additionalSigners,
695
950
  this.opts
696
951
  );
697
952
  return txSig;
@@ -839,7 +1094,7 @@ export class ClearingHouse {
839
1094
  const userAccountPublicKey = await this.getUserAccountPublicKey();
840
1095
 
841
1096
  const remainingAccounts = this.getRemainingAccounts({
842
- writableMarketIndex: orderParams.marketIndex,
1097
+ readableMarketIndex: orderParams.marketIndex,
843
1098
  });
844
1099
 
845
1100
  return await this.program.instruction.placeOrder(orderParams, {
@@ -982,24 +1237,13 @@ export class ClearingHouse {
982
1237
  const bankAccountMap = new Map<number, AccountMeta>();
983
1238
  const marketAccountMap = new Map<number, AccountMeta>();
984
1239
 
985
- marketAccountMap.set(marketIndex.toNumber(), {
986
- pubkey: marketAccount.pubkey,
987
- isWritable: true,
988
- isSigner: false,
989
- });
990
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
991
- pubkey: marketAccount.amm.oracle,
992
- isWritable: false,
993
- isSigner: false,
994
- });
995
-
996
1240
  for (const bankBalance of userAccount.bankBalances) {
997
1241
  if (!bankBalance.balance.eq(ZERO)) {
998
1242
  const bankAccount = this.getBankAccount(bankBalance.bankIndex);
999
1243
  bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1000
1244
  pubkey: bankAccount.pubkey,
1001
1245
  isSigner: false,
1002
- isWritable: true,
1246
+ isWritable: false,
1003
1247
  });
1004
1248
 
1005
1249
  if (!bankAccount.oracle.equals(PublicKey.default)) {
@@ -1020,7 +1264,7 @@ export class ClearingHouse {
1020
1264
  const market = this.getMarketAccount(position.marketIndex);
1021
1265
  marketAccountMap.set(position.marketIndex.toNumber(), {
1022
1266
  pubkey: market.pubkey,
1023
- isWritable: true,
1267
+ isWritable: false,
1024
1268
  isSigner: false,
1025
1269
  });
1026
1270
  oracleAccountMap.set(market.amm.oracle.toString(), {
@@ -1031,6 +1275,17 @@ export class ClearingHouse {
1031
1275
  }
1032
1276
  }
1033
1277
 
1278
+ marketAccountMap.set(marketIndex.toNumber(), {
1279
+ pubkey: marketAccount.pubkey,
1280
+ isWritable: true,
1281
+ isSigner: false,
1282
+ });
1283
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1284
+ pubkey: marketAccount.amm.oracle,
1285
+ isWritable: false,
1286
+ isSigner: false,
1287
+ });
1288
+
1034
1289
  const remainingAccounts = [
1035
1290
  ...oracleAccountMap.values(),
1036
1291
  ...bankAccountMap.values(),
@@ -1086,24 +1341,13 @@ export class ClearingHouse {
1086
1341
  const bankAccountMap = new Map<number, AccountMeta>();
1087
1342
  const marketAccountMap = new Map<number, AccountMeta>();
1088
1343
 
1089
- marketAccountMap.set(marketIndex.toNumber(), {
1090
- pubkey: marketAccount.pubkey,
1091
- isWritable: true,
1092
- isSigner: false,
1093
- });
1094
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1095
- pubkey: marketAccount.amm.oracle,
1096
- isWritable: false,
1097
- isSigner: false,
1098
- });
1099
-
1100
1344
  for (const bankBalance of userAccount.bankBalances) {
1101
1345
  if (!bankBalance.balance.eq(ZERO)) {
1102
1346
  const bankAccount = this.getBankAccount(bankBalance.bankIndex);
1103
1347
  bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1104
1348
  pubkey: bankAccount.pubkey,
1105
1349
  isSigner: false,
1106
- isWritable: true,
1350
+ isWritable: false,
1107
1351
  });
1108
1352
 
1109
1353
  if (!bankAccount.oracle.equals(PublicKey.default)) {
@@ -1124,7 +1368,7 @@ export class ClearingHouse {
1124
1368
  const market = this.getMarketAccount(position.marketIndex);
1125
1369
  marketAccountMap.set(position.marketIndex.toNumber(), {
1126
1370
  pubkey: market.pubkey,
1127
- isWritable: true,
1371
+ isWritable: false,
1128
1372
  isSigner: false,
1129
1373
  });
1130
1374
  oracleAccountMap.set(market.amm.oracle.toString(), {
@@ -1135,6 +1379,17 @@ export class ClearingHouse {
1135
1379
  }
1136
1380
  }
1137
1381
 
1382
+ marketAccountMap.set(marketIndex.toNumber(), {
1383
+ pubkey: marketAccount.pubkey,
1384
+ isWritable: true,
1385
+ isSigner: false,
1386
+ });
1387
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1388
+ pubkey: marketAccount.amm.oracle,
1389
+ isWritable: false,
1390
+ isSigner: false,
1391
+ });
1392
+
1138
1393
  const remainingAccounts = [
1139
1394
  ...oracleAccountMap.values(),
1140
1395
  ...bankAccountMap.values(),
@@ -1327,7 +1582,7 @@ export class ClearingHouse {
1327
1582
  const market = this.getMarketAccount(position.marketIndex);
1328
1583
  marketAccountMap.set(position.marketIndex.toNumber(), {
1329
1584
  pubkey: market.pubkey,
1330
- isWritable: true, // TODO
1585
+ isWritable: false,
1331
1586
  isSigner: false,
1332
1587
  });
1333
1588
  oracleAccountMap.set(market.amm.oracle.toString(), {
@@ -1390,72 +1645,359 @@ export class ClearingHouse {
1390
1645
  });
1391
1646
  }
1392
1647
 
1393
- public async liquidate(
1394
- liquidateeUserAccountPublicKey: PublicKey
1648
+ public async liquidatePerp(
1649
+ userAccountPublicKey: PublicKey,
1650
+ userAccount: UserAccount,
1651
+ marketIndex: BN,
1652
+ maxBaseAssetAmount: BN
1395
1653
  ): Promise<TransactionSignature> {
1396
1654
  const { txSig } = await this.txSender.send(
1397
- wrapInTx(await this.getLiquidateIx(liquidateeUserAccountPublicKey)),
1655
+ wrapInTx(
1656
+ await this.getLiquidatePerpIx(
1657
+ userAccountPublicKey,
1658
+ userAccount,
1659
+ marketIndex,
1660
+ maxBaseAssetAmount
1661
+ )
1662
+ ),
1398
1663
  [],
1399
1664
  this.opts
1400
1665
  );
1401
1666
  return txSig;
1402
1667
  }
1403
1668
 
1404
- public async getLiquidateIx(
1405
- liquidateeUserAccountPublicKey: PublicKey
1669
+ public async getLiquidatePerpIx(
1670
+ userAccountPublicKey: PublicKey,
1671
+ userAccount: UserAccount,
1672
+ marketIndex: BN,
1673
+ maxBaseAssetAmount: BN
1406
1674
  ): Promise<TransactionInstruction> {
1407
- const userAccountPublicKey = await this.getUserAccountPublicKey();
1408
- const liquidateeUserAccount = (await this.program.account.user.fetch(
1409
- liquidateeUserAccountPublicKey
1410
- )) as UserAccount;
1675
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
1676
+
1677
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
1678
+ writableMarketIndex: marketIndex,
1679
+ userAccount,
1680
+ });
1411
1681
 
1412
- const bankAccountInfos = [
1682
+ return await this.program.instruction.liquidatePerp(
1683
+ marketIndex,
1684
+ maxBaseAssetAmount,
1413
1685
  {
1414
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1415
- isSigner: false,
1416
- isWritable: true,
1417
- },
1418
- ];
1419
- const marketAccountInfos = [];
1420
- const oracleAccountInfos = [];
1686
+ accounts: {
1687
+ state: await this.getStatePublicKey(),
1688
+ authority: this.wallet.publicKey,
1689
+ user: userAccountPublicKey,
1690
+ liquidator: liquidatorPublicKey,
1691
+ },
1692
+ remainingAccounts: remainingAccounts,
1693
+ }
1694
+ );
1695
+ }
1696
+
1697
+ public async liquidateBorrow(
1698
+ userAccountPublicKey: PublicKey,
1699
+ userAccount: UserAccount,
1700
+ assetBankIndex: BN,
1701
+ liabilityBankIndex: BN,
1702
+ maxLiabilityTransfer: BN
1703
+ ): Promise<TransactionSignature> {
1704
+ const { txSig } = await this.txSender.send(
1705
+ wrapInTx(
1706
+ await this.getLiquidateBorrowIx(
1707
+ userAccountPublicKey,
1708
+ userAccount,
1709
+ assetBankIndex,
1710
+ liabilityBankIndex,
1711
+ maxLiabilityTransfer
1712
+ )
1713
+ ),
1714
+ [],
1715
+ this.opts
1716
+ );
1717
+ return txSig;
1718
+ }
1719
+
1720
+ public async getLiquidateBorrowIx(
1721
+ userAccountPublicKey: PublicKey,
1722
+ userAccount: UserAccount,
1723
+ assetBankIndex: BN,
1724
+ liabilityBankIndex: BN,
1725
+ maxLiabilityTransfer: BN
1726
+ ): Promise<TransactionInstruction> {
1727
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
1728
+
1729
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
1730
+ userAccount,
1731
+ writableBankIndexes: [liabilityBankIndex, assetBankIndex],
1732
+ });
1733
+
1734
+ return await this.program.instruction.liquidateBorrow(
1735
+ assetBankIndex,
1736
+ liabilityBankIndex,
1737
+ maxLiabilityTransfer,
1738
+ {
1739
+ accounts: {
1740
+ state: await this.getStatePublicKey(),
1741
+ authority: this.wallet.publicKey,
1742
+ user: userAccountPublicKey,
1743
+ liquidator: liquidatorPublicKey,
1744
+ },
1745
+ remainingAccounts: remainingAccounts,
1746
+ }
1747
+ );
1748
+ }
1749
+
1750
+ public async liquidateBorrowForPerpPnl(
1751
+ userAccountPublicKey: PublicKey,
1752
+ userAccount: UserAccount,
1753
+ perpMarketIndex: BN,
1754
+ liabilityBankIndex: BN,
1755
+ maxLiabilityTransfer: BN
1756
+ ): Promise<TransactionSignature> {
1757
+ const { txSig } = await this.txSender.send(
1758
+ wrapInTx(
1759
+ await this.getLiquidateBorrowForPerpPnlIx(
1760
+ userAccountPublicKey,
1761
+ userAccount,
1762
+ perpMarketIndex,
1763
+ liabilityBankIndex,
1764
+ maxLiabilityTransfer
1765
+ )
1766
+ ),
1767
+ [],
1768
+ this.opts
1769
+ );
1770
+ return txSig;
1771
+ }
1772
+
1773
+ public async getLiquidateBorrowForPerpPnlIx(
1774
+ userAccountPublicKey: PublicKey,
1775
+ userAccount: UserAccount,
1776
+ perpMarketIndex: BN,
1777
+ liabilityBankIndex: BN,
1778
+ maxLiabilityTransfer: BN
1779
+ ): Promise<TransactionInstruction> {
1780
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
1781
+
1782
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
1783
+ userAccount,
1784
+ writableMarketIndex: perpMarketIndex,
1785
+ writableBankIndexes: [liabilityBankIndex],
1786
+ });
1787
+
1788
+ return await this.program.instruction.liquidateBorrowForPerpPnl(
1789
+ perpMarketIndex,
1790
+ liabilityBankIndex,
1791
+ maxLiabilityTransfer,
1792
+ {
1793
+ accounts: {
1794
+ state: await this.getStatePublicKey(),
1795
+ authority: this.wallet.publicKey,
1796
+ user: userAccountPublicKey,
1797
+ liquidator: liquidatorPublicKey,
1798
+ },
1799
+ remainingAccounts: remainingAccounts,
1800
+ }
1801
+ );
1802
+ }
1803
+
1804
+ public async liquidatePerpPnlForDeposit(
1805
+ userAccountPublicKey: PublicKey,
1806
+ userAccount: UserAccount,
1807
+ perpMarketIndex: BN,
1808
+ assetBankIndex: BN,
1809
+ maxPnlTransfer: BN
1810
+ ): Promise<TransactionSignature> {
1811
+ const { txSig } = await this.txSender.send(
1812
+ wrapInTx(
1813
+ await this.getLiquidatePerpPnlForDepositIx(
1814
+ userAccountPublicKey,
1815
+ userAccount,
1816
+ perpMarketIndex,
1817
+ assetBankIndex,
1818
+ maxPnlTransfer
1819
+ )
1820
+ ),
1821
+ [],
1822
+ this.opts
1823
+ );
1824
+ return txSig;
1825
+ }
1826
+
1827
+ public async getLiquidatePerpPnlForDepositIx(
1828
+ userAccountPublicKey: PublicKey,
1829
+ userAccount: UserAccount,
1830
+ perpMarketIndex: BN,
1831
+ assetBankIndex: BN,
1832
+ maxPnlTransfer: BN
1833
+ ): Promise<TransactionInstruction> {
1834
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
1835
+
1836
+ const remainingAccounts = this.getRemainingAccountsForLiquidation({
1837
+ userAccount,
1838
+ writableMarketIndex: perpMarketIndex,
1839
+ writableBankIndexes: [assetBankIndex],
1840
+ });
1841
+
1842
+ return await this.program.instruction.liquidatePerpPnlForDeposit(
1843
+ perpMarketIndex,
1844
+ assetBankIndex,
1845
+ maxPnlTransfer,
1846
+ {
1847
+ accounts: {
1848
+ state: await this.getStatePublicKey(),
1849
+ authority: this.wallet.publicKey,
1850
+ user: userAccountPublicKey,
1851
+ liquidator: liquidatorPublicKey,
1852
+ },
1853
+ remainingAccounts: remainingAccounts,
1854
+ }
1855
+ );
1856
+ }
1857
+
1858
+ getRemainingAccountsForLiquidation(params: {
1859
+ userAccount: UserAccount;
1860
+ writableMarketIndex?: BN;
1861
+ writableBankIndexes?: BN[];
1862
+ }): AccountMeta[] {
1863
+ const liquidateeUserAccount = params.userAccount;
1864
+
1865
+ const oracleAccountMap = new Map<string, AccountMeta>();
1866
+ const bankAccountMap = new Map<number, AccountMeta>();
1867
+ const marketAccountMap = new Map<number, AccountMeta>();
1868
+ for (const bankBalance of liquidateeUserAccount.bankBalances) {
1869
+ if (!bankBalance.balance.eq(ZERO)) {
1870
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
1871
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1872
+ pubkey: bankAccount.pubkey,
1873
+ isSigner: false,
1874
+ isWritable: false,
1875
+ });
1876
+
1877
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
1878
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
1879
+ pubkey: bankAccount.oracle,
1880
+ isSigner: false,
1881
+ isWritable: false,
1882
+ });
1883
+ }
1884
+ }
1885
+ }
1421
1886
  for (const position of liquidateeUserAccount.positions) {
1422
1887
  if (!positionIsAvailable(position)) {
1423
1888
  const market = this.getMarketAccount(position.marketIndex);
1424
- const marketPublicKey = await getMarketPublicKey(
1425
- this.program.programId,
1426
- position.marketIndex
1427
- );
1428
- marketAccountInfos.push({
1429
- pubkey: marketPublicKey,
1430
- isWritable: true,
1889
+ marketAccountMap.set(position.marketIndex.toNumber(), {
1890
+ pubkey: market.pubkey,
1891
+ isWritable: false,
1431
1892
  isSigner: false,
1432
1893
  });
1433
- oracleAccountInfos.push({
1894
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1434
1895
  pubkey: market.amm.oracle,
1435
1896
  isWritable: false,
1436
1897
  isSigner: false,
1437
1898
  });
1438
1899
  }
1439
1900
  }
1440
- const remainingAccounts = oracleAccountInfos.concat(
1441
- bankAccountInfos.concat(marketAccountInfos)
1442
- );
1443
1901
 
1444
- const state = this.getStateAccount();
1445
- const quoteAssetBankAccount = this.getQuoteAssetBankAccount();
1446
- return await this.program.instruction.liquidate({
1447
- accounts: {
1448
- state: await this.getStatePublicKey(),
1449
- authority: this.wallet.publicKey,
1450
- user: liquidateeUserAccountPublicKey,
1451
- liquidator: userAccountPublicKey,
1452
- bankVault: quoteAssetBankAccount.vault,
1453
- bankVaultAuthority: quoteAssetBankAccount.vaultAuthority,
1454
- insuranceVault: state.insuranceVault,
1455
- tokenProgram: TOKEN_PROGRAM_ID,
1456
- },
1457
- remainingAccounts: remainingAccounts,
1458
- });
1902
+ const userAccountAndSlot = this.getUserAccountAndSlot();
1903
+ if (!userAccountAndSlot) {
1904
+ throw Error(
1905
+ 'No user account found. Most likely user account does not exist or failed to fetch account'
1906
+ );
1907
+ }
1908
+ const { data: userAccount, slot: lastUserPositionsSlot } =
1909
+ userAccountAndSlot;
1910
+
1911
+ for (const [marketIndexNum, slot] of this.marketLastSlotCache.entries()) {
1912
+ // if cache has more recent slot than user positions account slot, add market to remaining accounts
1913
+ // otherwise remove from slot
1914
+ if (slot > lastUserPositionsSlot) {
1915
+ const marketAccount = this.getMarketAccount(marketIndexNum);
1916
+ marketAccountMap.set(marketIndexNum, {
1917
+ pubkey: marketAccount.pubkey,
1918
+ isSigner: false,
1919
+ isWritable: false,
1920
+ });
1921
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1922
+ pubkey: marketAccount.amm.oracle,
1923
+ isSigner: false,
1924
+ isWritable: false,
1925
+ });
1926
+ } else {
1927
+ this.marketLastSlotCache.delete(marketIndexNum);
1928
+ }
1929
+ }
1930
+ for (const bankBalance of userAccount.bankBalances) {
1931
+ if (!bankBalance.balance.eq(ZERO)) {
1932
+ const bankAccount = this.getBankAccount(bankBalance.bankIndex);
1933
+ bankAccountMap.set(bankBalance.bankIndex.toNumber(), {
1934
+ pubkey: bankAccount.pubkey,
1935
+ isSigner: false,
1936
+ isWritable: false,
1937
+ });
1938
+
1939
+ if (!bankAccount.oracle.equals(PublicKey.default)) {
1940
+ oracleAccountMap.set(bankAccount.oracle.toString(), {
1941
+ pubkey: bankAccount.oracle,
1942
+ isSigner: false,
1943
+ isWritable: false,
1944
+ });
1945
+ }
1946
+ }
1947
+ }
1948
+ for (const position of userAccount.positions) {
1949
+ if (!positionIsAvailable(position)) {
1950
+ const market = this.getMarketAccount(position.marketIndex);
1951
+ marketAccountMap.set(position.marketIndex.toNumber(), {
1952
+ pubkey: market.pubkey,
1953
+ isWritable: false,
1954
+ isSigner: false,
1955
+ });
1956
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1957
+ pubkey: market.amm.oracle,
1958
+ isWritable: false,
1959
+ isSigner: false,
1960
+ });
1961
+ }
1962
+ }
1963
+
1964
+ if (params.writableMarketIndex) {
1965
+ const market = this.getMarketAccount(params.writableMarketIndex);
1966
+ marketAccountMap.set(market.marketIndex.toNumber(), {
1967
+ pubkey: market.pubkey,
1968
+ isSigner: false,
1969
+ isWritable: true,
1970
+ });
1971
+ oracleAccountMap.set(market.amm.oracle.toString(), {
1972
+ pubkey: market.amm.oracle,
1973
+ isSigner: false,
1974
+ isWritable: false,
1975
+ });
1976
+ }
1977
+
1978
+ if (params.writableBankIndexes) {
1979
+ for (const writableBankIndex of params.writableBankIndexes) {
1980
+ const bank = this.getBankAccount(writableBankIndex);
1981
+ bankAccountMap.set(bank.bankIndex.toNumber(), {
1982
+ pubkey: bank.pubkey,
1983
+ isSigner: false,
1984
+ isWritable: true,
1985
+ });
1986
+ if (!bank.oracle.equals(PublicKey.default)) {
1987
+ oracleAccountMap.set(bank.oracle.toString(), {
1988
+ pubkey: bank.oracle,
1989
+ isSigner: false,
1990
+ isWritable: false,
1991
+ });
1992
+ }
1993
+ }
1994
+ }
1995
+
1996
+ return [
1997
+ ...oracleAccountMap.values(),
1998
+ ...bankAccountMap.values(),
1999
+ ...marketAccountMap.values(),
2000
+ ];
1459
2001
  }
1460
2002
 
1461
2003
  public async updateFundingRate(