@drift-labs/sdk 0.2.0-master.3 → 0.2.0-master.31

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 (208) hide show
  1. package/README.md +13 -13
  2. package/lib/accounts/bulkUserStatsSubscription.d.ts +7 -0
  3. package/lib/accounts/bulkUserStatsSubscription.js +21 -0
  4. package/lib/accounts/bulkUserSubscription.js +0 -1
  5. package/lib/accounts/fetch.d.ts +2 -1
  6. package/lib/accounts/fetch.js +9 -1
  7. package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +16 -16
  8. package/lib/accounts/pollingClearingHouseAccountSubscriber.js +31 -28
  9. package/lib/accounts/pollingUserStatsAccountSubscriber.d.ts +27 -0
  10. package/lib/accounts/pollingUserStatsAccountSubscriber.js +113 -0
  11. package/lib/accounts/types.d.ts +22 -11
  12. package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +17 -17
  13. package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +37 -35
  14. package/lib/accounts/webSocketUserStatsAccountSubsriber.d.ts +20 -0
  15. package/lib/accounts/webSocketUserStatsAccountSubsriber.js +47 -0
  16. package/lib/addresses/marketAddresses.d.ts +1 -3
  17. package/lib/addresses/pda.d.ts +10 -4
  18. package/lib/addresses/pda.js +51 -14
  19. package/lib/admin.d.ts +42 -26
  20. package/lib/admin.js +228 -81
  21. package/lib/clearingHouse.d.ts +130 -49
  22. package/lib/clearingHouse.js +1256 -367
  23. package/lib/clearingHouseConfig.d.ts +4 -4
  24. package/lib/clearingHouseUser.d.ts +51 -39
  25. package/lib/clearingHouseUser.js +408 -191
  26. package/lib/clearingHouseUserStats.d.ts +18 -0
  27. package/lib/clearingHouseUserStats.js +49 -0
  28. package/lib/clearingHouseUserStatsConfig.d.ts +14 -0
  29. package/lib/clearingHouseUserStatsConfig.js +2 -0
  30. package/lib/config.d.ts +7 -9
  31. package/lib/config.js +21 -21
  32. package/lib/constants/numericConstants.d.ts +25 -12
  33. package/lib/constants/numericConstants.js +35 -21
  34. package/lib/constants/perpMarkets.d.ts +18 -0
  35. package/lib/constants/{markets.js → perpMarkets.js} +7 -7
  36. package/lib/constants/spotMarkets.d.ts +19 -0
  37. package/lib/constants/spotMarkets.js +53 -0
  38. package/lib/dlob/DLOB.d.ts +74 -0
  39. package/lib/dlob/DLOB.js +595 -0
  40. package/lib/dlob/DLOBNode.d.ts +54 -0
  41. package/lib/dlob/DLOBNode.js +85 -0
  42. package/lib/dlob/NodeList.d.ts +26 -0
  43. package/lib/dlob/NodeList.js +139 -0
  44. package/lib/events/eventList.js +3 -0
  45. package/lib/events/eventSubscriber.d.ts +4 -2
  46. package/lib/events/eventSubscriber.js +16 -9
  47. package/lib/events/fetchLogs.d.ts +10 -1
  48. package/lib/events/fetchLogs.js +27 -7
  49. package/lib/events/pollingLogProvider.d.ts +2 -1
  50. package/lib/events/pollingLogProvider.js +6 -2
  51. package/lib/events/sort.js +8 -11
  52. package/lib/events/types.d.ts +8 -2
  53. package/lib/events/types.js +6 -0
  54. package/lib/examples/makeTradeExample.js +20 -8
  55. package/lib/factory/bigNum.d.ts +2 -0
  56. package/lib/factory/bigNum.js +48 -10
  57. package/lib/idl/clearing_house.json +4889 -1529
  58. package/lib/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  59. package/lib/index.d.ts +21 -6
  60. package/lib/index.js +25 -6
  61. package/lib/math/amm.d.ts +9 -6
  62. package/lib/math/amm.js +91 -66
  63. package/lib/math/auction.js +4 -1
  64. package/lib/math/conversion.js +1 -1
  65. package/lib/math/funding.d.ts +6 -6
  66. package/lib/math/funding.js +11 -10
  67. package/lib/math/insurance.d.ts +4 -0
  68. package/lib/math/insurance.js +27 -0
  69. package/lib/math/margin.d.ts +11 -0
  70. package/lib/math/margin.js +77 -0
  71. package/lib/math/market.d.ts +14 -9
  72. package/lib/math/market.js +72 -10
  73. package/lib/math/oracles.d.ts +4 -0
  74. package/lib/math/oracles.js +36 -8
  75. package/lib/math/orders.d.ts +6 -2
  76. package/lib/math/orders.js +78 -3
  77. package/lib/math/position.d.ts +21 -13
  78. package/lib/math/position.js +76 -36
  79. package/lib/math/repeg.js +14 -5
  80. package/lib/math/spotBalance.d.ts +22 -0
  81. package/lib/math/spotBalance.js +192 -0
  82. package/lib/math/spotMarket.d.ts +4 -0
  83. package/lib/math/spotMarket.js +8 -0
  84. package/lib/math/spotPosition.d.ts +6 -0
  85. package/lib/math/spotPosition.js +23 -0
  86. package/lib/math/trade.d.ts +10 -10
  87. package/lib/math/trade.js +32 -39
  88. package/lib/oracles/pythClient.js +1 -1
  89. package/lib/oracles/quoteAssetOracleClient.js +1 -1
  90. package/lib/oracles/switchboardClient.js +1 -1
  91. package/lib/orderParams.d.ts +14 -5
  92. package/lib/orderParams.js +12 -92
  93. package/lib/serum/serumSubscriber.d.ts +23 -0
  94. package/lib/serum/serumSubscriber.js +41 -0
  95. package/lib/serum/types.d.ts +11 -0
  96. package/lib/serum/types.js +2 -0
  97. package/lib/slot/SlotSubscriber.d.ts +7 -0
  98. package/lib/slot/SlotSubscriber.js +3 -0
  99. package/lib/{mockUSDCFaucet.d.ts → tokenFaucet.d.ts} +8 -5
  100. package/lib/{mockUSDCFaucet.js → tokenFaucet.js} +63 -51
  101. package/lib/tx/retryTxSender.d.ts +1 -1
  102. package/lib/tx/retryTxSender.js +13 -4
  103. package/lib/tx/types.d.ts +1 -1
  104. package/lib/tx/utils.js +1 -1
  105. package/lib/types.d.ts +566 -138
  106. package/lib/types.js +129 -5
  107. package/lib/userMap/userMap.d.ts +25 -0
  108. package/lib/userMap/userMap.js +73 -0
  109. package/lib/userMap/userStatsMap.d.ts +19 -0
  110. package/lib/userMap/userStatsMap.js +68 -0
  111. package/lib/util/computeUnits.js +1 -1
  112. package/lib/util/getTokenAddress.d.ts +2 -0
  113. package/lib/util/getTokenAddress.js +9 -0
  114. package/package.json +12 -6
  115. package/src/accounts/bulkUserStatsSubscription.ts +33 -0
  116. package/src/accounts/bulkUserSubscription.ts +0 -1
  117. package/src/accounts/fetch.ts +27 -2
  118. package/src/accounts/pollingClearingHouseAccountSubscriber.ts +46 -42
  119. package/src/accounts/pollingUserStatsAccountSubscriber.ts +172 -0
  120. package/src/accounts/types.ts +31 -11
  121. package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +64 -59
  122. package/src/accounts/webSocketUserStatsAccountSubsriber.ts +80 -0
  123. package/src/addresses/marketAddresses.ts +1 -2
  124. package/src/addresses/pda.ts +88 -14
  125. package/src/admin.ts +452 -147
  126. package/src/assert/assert.js +9 -0
  127. package/src/clearingHouse.ts +2066 -467
  128. package/src/clearingHouseConfig.ts +4 -3
  129. package/src/clearingHouseUser.ts +747 -294
  130. package/src/clearingHouseUserStats.ts +75 -0
  131. package/src/clearingHouseUserStatsConfig.ts +18 -0
  132. package/src/config.ts +30 -31
  133. package/src/constants/numericConstants.ts +48 -25
  134. package/src/constants/{markets.ts → perpMarkets.ts} +10 -10
  135. package/src/constants/spotMarkets.ts +73 -0
  136. package/src/dlob/DLOB.ts +953 -0
  137. package/src/dlob/DLOBNode.ts +167 -0
  138. package/src/dlob/NodeList.ts +189 -0
  139. package/src/events/eventList.js +77 -0
  140. package/src/events/eventList.ts +3 -0
  141. package/src/events/eventSubscriber.ts +20 -12
  142. package/src/events/fetchLogs.ts +35 -8
  143. package/src/events/pollingLogProvider.ts +10 -2
  144. package/src/events/sort.ts +11 -15
  145. package/src/events/types.ts +19 -1
  146. package/src/examples/makeTradeExample.js +157 -0
  147. package/src/examples/makeTradeExample.ts +32 -14
  148. package/src/factory/bigNum.ts +65 -13
  149. package/src/idl/clearing_house.json +4889 -1529
  150. package/src/idl/{mock_usdc_faucet.json → token_faucet.json} +46 -23
  151. package/src/index.ts +21 -6
  152. package/src/math/amm.ts +171 -88
  153. package/src/math/auction.ts +5 -1
  154. package/src/math/conversion.ts +2 -2
  155. package/src/math/funding.ts +20 -18
  156. package/src/math/insurance.ts +35 -0
  157. package/src/math/margin.ts +127 -0
  158. package/src/math/market.ts +145 -14
  159. package/src/math/oracles.ts +63 -9
  160. package/src/math/orders.ts +138 -4
  161. package/src/math/position.ts +119 -58
  162. package/src/math/repeg.ts +16 -6
  163. package/src/math/spotBalance.ts +319 -0
  164. package/src/math/spotMarket.ts +9 -0
  165. package/src/math/spotPosition.ts +47 -0
  166. package/src/math/trade.ts +53 -59
  167. package/src/oracles/pythClient.ts +2 -2
  168. package/src/oracles/quoteAssetOracleClient.ts +2 -2
  169. package/src/oracles/switchboardClient.ts +2 -2
  170. package/src/orderParams.ts +24 -137
  171. package/src/serum/serumSubscriber.ts +80 -0
  172. package/src/serum/types.ts +13 -0
  173. package/src/slot/SlotSubscriber.ts +11 -1
  174. package/src/token/index.js +38 -0
  175. package/src/{mockUSDCFaucet.ts → tokenFaucet.ts} +82 -70
  176. package/src/tx/retryTxSender.ts +16 -5
  177. package/src/tx/types.js +2 -0
  178. package/src/tx/types.ts +2 -1
  179. package/src/tx/utils.js +17 -0
  180. package/src/tx/utils.ts +1 -1
  181. package/src/types.ts +533 -140
  182. package/src/userMap/userMap.ts +100 -0
  183. package/src/userMap/userStatsMap.ts +110 -0
  184. package/src/util/computeUnits.js +21 -11
  185. package/src/util/computeUnits.ts +1 -1
  186. package/src/util/getTokenAddress.js +9 -0
  187. package/src/util/getTokenAddress.ts +18 -0
  188. package/src/util/promiseTimeout.js +14 -0
  189. package/src/util/tps.js +27 -0
  190. package/tests/bn/test.ts +24 -10
  191. package/tests/dlob/helpers.ts +397 -0
  192. package/tests/dlob/test.ts +3688 -0
  193. package/lib/constants/banks.d.ts +0 -16
  194. package/lib/constants/banks.js +0 -34
  195. package/lib/constants/markets.d.ts +0 -19
  196. package/lib/math/bankBalance.d.ts +0 -9
  197. package/lib/math/bankBalance.js +0 -75
  198. package/lib/math/state.d.ts +0 -8
  199. package/lib/math/state.js +0 -15
  200. package/lib/orders.d.ts +0 -8
  201. package/lib/orders.js +0 -134
  202. package/src/constants/banks.ts +0 -43
  203. package/src/math/bankBalance.ts +0 -112
  204. package/src/math/state.ts +0 -14
  205. package/src/math/utils.js +0 -27
  206. package/src/math/utils.js.map +0 -1
  207. package/src/orders.ts +0 -244
  208. package/src/util/computeUnits.js.map +0 -1
@@ -1,16 +1,29 @@
1
1
  import { AnchorProvider, BN, Idl, Program } from '@project-serum/anchor';
2
- import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
2
+ import bs58 from 'bs58';
3
+ import {
4
+ ASSOCIATED_TOKEN_PROGRAM_ID,
5
+ Token,
6
+ TOKEN_PROGRAM_ID,
7
+ } from '@solana/spl-token';
3
8
  import {
4
9
  StateAccount,
5
10
  IWallet,
6
11
  PositionDirection,
7
12
  UserAccount,
8
- MarketAccount,
13
+ PerpMarketAccount,
9
14
  OrderParams,
10
15
  Order,
11
- BankAccount,
12
- UserBankBalance,
16
+ SpotMarketAccount,
17
+ SpotPosition,
13
18
  MakerInfo,
19
+ TakerInfo,
20
+ OptionalOrderParams,
21
+ DefaultOrderParams,
22
+ OrderType,
23
+ ReferrerInfo,
24
+ MarketType,
25
+ SerumV3FulfillmentConfigAccount,
26
+ isVariant,
14
27
  } from './types';
15
28
  import * as anchor from '@project-serum/anchor';
16
29
  import clearingHouseIDL from './idl/clearing_house.json';
@@ -23,16 +36,26 @@ import {
23
36
  Transaction,
24
37
  TransactionInstruction,
25
38
  AccountMeta,
39
+ Keypair,
40
+ LAMPORTS_PER_SOL,
41
+ Signer,
42
+ SystemProgram,
43
+ ComputeBudgetProgram,
26
44
  } from '@solana/web3.js';
27
45
 
28
- import { MockUSDCFaucet } from './mockUSDCFaucet';
46
+ import { TokenFaucet } from './tokenFaucet';
29
47
  import { EventEmitter } from 'events';
30
48
  import StrictEventEmitter from 'strict-event-emitter-types';
31
49
  import {
50
+ getClearingHouseSignerPublicKey,
32
51
  getClearingHouseStateAccountPublicKey,
52
+ getInsuranceFundStakeAccountPublicKey,
33
53
  getMarketPublicKey,
54
+ getSerumFulfillmentConfigPublicKey,
55
+ getSerumSignerPublicKey,
34
56
  getUserAccountPublicKey,
35
57
  getUserAccountPublicKeySync,
58
+ getUserStatsAccountPublicKey,
36
59
  } from './addresses/pda';
37
60
  import {
38
61
  ClearingHouseAccountSubscriber,
@@ -41,9 +64,9 @@ import {
41
64
  } from './accounts/types';
42
65
  import { TxSender } from './tx/types';
43
66
  import { wrapInTx } from './tx/utils';
44
- import { QUOTE_ASSET_BANK_INDEX, ZERO } from './constants/numericConstants';
67
+ import { QUOTE_SPOT_MARKET_INDEX } from './constants/numericConstants';
45
68
  import { findDirectionToClose, positionIsAvailable } from './math/position';
46
- import { getTokenAmount } from './math/bankBalance';
69
+ import { getTokenAmount } from './math/spotBalance';
47
70
  import { DEFAULT_USER_NAME, encodeName } from './userName';
48
71
  import { OraclePriceData } from './oracles/types';
49
72
  import { ClearingHouseConfig } from './clearingHouseConfig';
@@ -52,8 +75,19 @@ import { WebSocketClearingHouseAccountSubscriber } from './accounts/webSocketCle
52
75
  import { RetryTxSender } from './tx/retryTxSender';
53
76
  import { ClearingHouseUser } from './clearingHouseUser';
54
77
  import { ClearingHouseUserAccountSubscriptionConfig } from './clearingHouseUserConfig';
55
- import { getMarketOrderParams } from './orderParams';
56
- import { getMarketsBanksAndOraclesForSubscription } from './config';
78
+ import { getMarketsAndOraclesForSubscription } from './config';
79
+ import { WRAPPED_SOL_MINT } from './constants/spotMarkets';
80
+ import { ClearingHouseUserStats } from './clearingHouseUserStats';
81
+ import { isSpotPositionAvailable } from './math/spotPosition';
82
+
83
+ type RemainingAccountParams = {
84
+ userAccounts: UserAccount[];
85
+ writablePerpMarketIndexes?: number[];
86
+ writableSpotMarketIndexes?: number[];
87
+ readablePerpMarketIndex?: number;
88
+ readableSpotMarketIndex?: number;
89
+ useMarketLastSlotCache?: boolean;
90
+ };
57
91
 
58
92
  /**
59
93
  * # ClearingHouse
@@ -66,6 +100,7 @@ export class ClearingHouse {
66
100
  provider: AnchorProvider;
67
101
  opts?: ConfirmOptions;
68
102
  users = new Map<number, ClearingHouseUser>();
103
+ userStats?: ClearingHouseUserStats;
69
104
  activeUserId: number;
70
105
  userAccountSubscriptionConfig: ClearingHouseUserAccountSubscriptionConfig;
71
106
  accountSubscriber: ClearingHouseAccountSubscriber;
@@ -73,6 +108,7 @@ export class ClearingHouse {
73
108
  _isSubscribed = false;
74
109
  txSender: TxSender;
75
110
  marketLastSlotCache = new Map<number, number>();
111
+ authority: PublicKey;
76
112
 
77
113
  public get isSubscribed() {
78
114
  return this._isSubscribed && this.accountSubscriber.isSubscribed;
@@ -97,6 +133,7 @@ export class ClearingHouse {
97
133
  this.provider
98
134
  );
99
135
 
136
+ this.authority = config.authority ?? this.wallet.publicKey;
100
137
  const userIds = config.userIds ?? [0];
101
138
  this.activeUserId = config.activeUserId ?? userIds[0];
102
139
  this.userAccountSubscriptionConfig =
@@ -109,18 +146,32 @@ export class ClearingHouse {
109
146
  type: 'websocket',
110
147
  };
111
148
  this.createUsers(userIds, this.userAccountSubscriptionConfig);
149
+ if (config.userStats) {
150
+ this.userStats = new ClearingHouseUserStats({
151
+ clearingHouse: this,
152
+ userStatsAccountPublicKey: getUserStatsAccountPublicKey(
153
+ this.program.programId,
154
+ this.authority
155
+ ),
156
+ accountSubscription: this.userAccountSubscriptionConfig,
157
+ });
158
+ }
112
159
 
113
- let marketIndexes = config.marketIndexes;
114
- let bankIndexes = config.bankIndexes;
160
+ let perpMarketIndexes = config.perpMarketIndexes;
161
+ let spotMarketIndexes = config.spotMarketIndexes;
115
162
  let oracleInfos = config.oracleInfos;
116
163
  if (config.env) {
117
164
  const {
118
- marketIndexes: envMarketIndexes,
119
- bankIndexes: envBankIndexes,
165
+ perpMarketIndexes: envPerpMarketIndexes,
166
+ spotMarketIndexes: envSpotMarketIndexes,
120
167
  oracleInfos: envOralceInfos,
121
- } = getMarketsBanksAndOraclesForSubscription(config.env);
122
- marketIndexes = marketIndexes ? marketIndexes : envMarketIndexes;
123
- bankIndexes = bankIndexes ? bankIndexes : envBankIndexes;
168
+ } = getMarketsAndOraclesForSubscription(config.env);
169
+ perpMarketIndexes = perpMarketIndexes
170
+ ? perpMarketIndexes
171
+ : envPerpMarketIndexes;
172
+ spotMarketIndexes = spotMarketIndexes
173
+ ? spotMarketIndexes
174
+ : envSpotMarketIndexes;
124
175
  oracleInfos = oracleInfos ? oracleInfos : envOralceInfos;
125
176
  }
126
177
 
@@ -128,15 +179,15 @@ export class ClearingHouse {
128
179
  this.accountSubscriber = new PollingClearingHouseAccountSubscriber(
129
180
  this.program,
130
181
  config.accountSubscription.accountLoader,
131
- marketIndexes ?? [],
132
- bankIndexes ?? [],
182
+ perpMarketIndexes ?? [],
183
+ spotMarketIndexes ?? [],
133
184
  oracleInfos ?? []
134
185
  );
135
186
  } else {
136
187
  this.accountSubscriber = new WebSocketClearingHouseAccountSubscriber(
137
188
  this.program,
138
- config.marketIndexes ?? [],
139
- config.bankIndexes ?? [],
189
+ config.perpMarketIndexes ?? [],
190
+ config.spotMarketIndexes ?? [],
140
191
  config.oracleInfos ?? []
141
192
  );
142
193
  }
@@ -165,7 +216,7 @@ export class ClearingHouse {
165
216
  ): ClearingHouseUser {
166
217
  const userAccountPublicKey = getUserAccountPublicKeySync(
167
218
  this.program.programId,
168
- this.wallet.publicKey,
219
+ this.authority,
169
220
  userId
170
221
  );
171
222
 
@@ -180,6 +231,9 @@ export class ClearingHouse {
180
231
  const subscribePromises = this.subscribeUsers().concat(
181
232
  this.accountSubscriber.subscribe()
182
233
  );
234
+ if (this.userStats !== undefined) {
235
+ subscribePromises.concat(this.userStats.subscribe());
236
+ }
183
237
  this.isSubscribed = (await Promise.all(subscribePromises)).reduce(
184
238
  (success, prevSuccess) => success && prevSuccess
185
239
  );
@@ -194,17 +248,22 @@ export class ClearingHouse {
194
248
  * Forces the accountSubscriber to fetch account updates from rpc
195
249
  */
196
250
  public async fetchAccounts(): Promise<void> {
197
- await Promise.all(
198
- [...this.users.values()]
199
- .map((user) => user.fetchAccounts())
200
- .concat(this.accountSubscriber.fetch())
201
- );
251
+ const promises = [...this.users.values()]
252
+ .map((user) => user.fetchAccounts())
253
+ .concat(this.accountSubscriber.fetch());
254
+ if (this.userStats) {
255
+ promises.concat(this.userStats.fetchAccounts());
256
+ }
257
+ await Promise.all(promises);
202
258
  }
203
259
 
204
260
  public async unsubscribe(): Promise<void> {
205
261
  const unsubscribePromises = this.unsubscribeUsers().concat(
206
262
  this.accountSubscriber.unsubscribe()
207
263
  );
264
+ if (this.userStats !== undefined) {
265
+ unsubscribePromises.concat(this.userStats.unsubscribe());
266
+ }
208
267
  await Promise.all(unsubscribePromises);
209
268
  this.isSubscribed = false;
210
269
  }
@@ -224,29 +283,49 @@ export class ClearingHouse {
224
283
  return this.statePublicKey;
225
284
  }
226
285
 
286
+ signerPublicKey?: PublicKey;
287
+ public getSignerPublicKey(): PublicKey {
288
+ if (this.signerPublicKey) {
289
+ return this.signerPublicKey;
290
+ }
291
+ this.signerPublicKey = getClearingHouseSignerPublicKey(
292
+ this.program.programId
293
+ );
294
+ return this.signerPublicKey;
295
+ }
296
+
227
297
  public getStateAccount(): StateAccount {
228
298
  return this.accountSubscriber.getStateAccountAndSlot().data;
229
299
  }
230
300
 
231
- public getMarketAccount(marketIndex: BN | number): MarketAccount | undefined {
232
- marketIndex = marketIndex instanceof BN ? marketIndex : new BN(marketIndex);
301
+ public getPerpMarketAccount(
302
+ marketIndex: number
303
+ ): PerpMarketAccount | undefined {
233
304
  return this.accountSubscriber.getMarketAccountAndSlot(marketIndex)?.data;
234
305
  }
235
306
 
236
- public getMarketAccounts(): MarketAccount[] {
307
+ public getPerpMarketAccounts(): PerpMarketAccount[] {
237
308
  return this.accountSubscriber
238
309
  .getMarketAccountsAndSlots()
239
310
  .map((value) => value.data);
240
311
  }
241
312
 
242
- public getBankAccount(bankIndex: BN | number): BankAccount | undefined {
243
- bankIndex = bankIndex instanceof BN ? bankIndex : new BN(bankIndex);
244
- return this.accountSubscriber.getBankAccountAndSlot(bankIndex).data;
313
+ public getSpotMarketAccount(
314
+ marketIndex: number
315
+ ): SpotMarketAccount | undefined {
316
+ return this.accountSubscriber.getSpotMarketAccountAndSlot(marketIndex).data;
317
+ }
318
+
319
+ public getSpotMarketAccounts(): SpotMarketAccount[] {
320
+ return this.accountSubscriber
321
+ .getSpotMarketAccountsAndSlots()
322
+ .map((value) => value.data);
245
323
  }
246
324
 
247
- public getQuoteAssetBankAccount(): BankAccount {
248
- return this.accountSubscriber.getBankAccountAndSlot(QUOTE_ASSET_BANK_INDEX)
249
- .data;
325
+ public getQuoteSpotMarketAccount(): SpotMarketAccount {
326
+ return this.accountSubscriber.getSpotMarketAccountAndSlot(
327
+ QUOTE_SPOT_MARKET_INDEX
328
+ ).data;
250
329
  }
251
330
 
252
331
  public getOraclePriceDataAndSlot(
@@ -255,6 +334,18 @@ export class ClearingHouse {
255
334
  return this.accountSubscriber.getOraclePriceDataAndSlot(oraclePublicKey);
256
335
  }
257
336
 
337
+ public async getSerumV3FulfillmentConfig(
338
+ serumMarket: PublicKey
339
+ ): Promise<SerumV3FulfillmentConfigAccount> {
340
+ const address = await getSerumFulfillmentConfigPublicKey(
341
+ this.program.programId,
342
+ serumMarket
343
+ );
344
+ return (await this.program.account.serumV3FulfillmentConfig.fetch(
345
+ address
346
+ )) as SerumV3FulfillmentConfigAccount;
347
+ }
348
+
258
349
  /**
259
350
  * Update the wallet to use for clearing house transactions and linked user account
260
351
  * @param newWallet
@@ -283,6 +374,7 @@ export class ClearingHouse {
283
374
  this.wallet = newWallet;
284
375
  this.provider = newProvider;
285
376
  this.program = newProgram;
377
+ this.authority = newWallet.publicKey;
286
378
 
287
379
  if (this.isSubscribed) {
288
380
  await Promise.all(this.unsubscribeUsers());
@@ -294,6 +386,7 @@ export class ClearingHouse {
294
386
  }
295
387
 
296
388
  this.activeUserId = activeUserId;
389
+ this.userStatsAccountPublicKey = undefined;
297
390
  }
298
391
 
299
392
  public async switchActiveUser(userId: number): Promise<void> {
@@ -312,19 +405,26 @@ export class ClearingHouse {
312
405
 
313
406
  public async initializeUserAccount(
314
407
  userId = 0,
315
- name = DEFAULT_USER_NAME
408
+ name = DEFAULT_USER_NAME,
409
+ referrerInfo?: ReferrerInfo
316
410
  ): Promise<[TransactionSignature, PublicKey]> {
317
411
  const [userAccountPublicKey, initializeUserAccountIx] =
318
- await this.getInitializeUserInstructions(userId, name);
412
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
319
413
 
320
- const tx = new Transaction().add(initializeUserAccountIx);
414
+ const tx = new Transaction();
415
+ if (userId === 0) {
416
+ // not the safest assumption, can explicitly check if user stats account exists if it causes problems
417
+ tx.add(await this.getInitializeUserStatsIx());
418
+ }
419
+ tx.add(initializeUserAccountIx);
321
420
  const { txSig } = await this.txSender.send(tx, [], this.opts);
322
421
  return [txSig, userAccountPublicKey];
323
422
  }
324
423
 
325
424
  async getInitializeUserInstructions(
326
425
  userId = 0,
327
- name = DEFAULT_USER_NAME
426
+ name = DEFAULT_USER_NAME,
427
+ referrerInfo?: ReferrerInfo
328
428
  ): Promise<[PublicKey, TransactionInstruction]> {
329
429
  const userAccountPublicKey = await getUserAccountPublicKey(
330
430
  this.program.programId,
@@ -332,22 +432,147 @@ export class ClearingHouse {
332
432
  userId
333
433
  );
334
434
 
435
+ const remainingAccounts = new Array<AccountMeta>();
436
+ if (referrerInfo !== undefined) {
437
+ remainingAccounts.push({
438
+ pubkey: referrerInfo.referrer,
439
+ isWritable: true,
440
+ isSigner: false,
441
+ });
442
+ remainingAccounts.push({
443
+ pubkey: referrerInfo.referrerStats,
444
+ isWritable: true,
445
+ isSigner: false,
446
+ });
447
+ }
448
+
449
+ const state = this.getStateAccount();
450
+ if (!state.whitelistMint.equals(PublicKey.default)) {
451
+ const associatedTokenPublicKey = await Token.getAssociatedTokenAddress(
452
+ ASSOCIATED_TOKEN_PROGRAM_ID,
453
+ TOKEN_PROGRAM_ID,
454
+ state.whitelistMint,
455
+ this.wallet.publicKey
456
+ );
457
+ remainingAccounts.push({
458
+ pubkey: associatedTokenPublicKey,
459
+ isWritable: false,
460
+ isSigner: false,
461
+ });
462
+ }
463
+
335
464
  const nameBuffer = encodeName(name);
336
465
  const initializeUserAccountIx =
337
466
  await this.program.instruction.initializeUser(userId, nameBuffer, {
338
467
  accounts: {
339
468
  user: userAccountPublicKey,
469
+ userStats: this.getUserStatsAccountPublicKey(),
340
470
  authority: this.wallet.publicKey,
341
471
  payer: this.wallet.publicKey,
342
472
  rent: anchor.web3.SYSVAR_RENT_PUBKEY,
343
473
  systemProgram: anchor.web3.SystemProgram.programId,
344
474
  state: await this.getStatePublicKey(),
345
475
  },
476
+ remainingAccounts,
346
477
  });
347
478
 
348
479
  return [userAccountPublicKey, initializeUserAccountIx];
349
480
  }
350
481
 
482
+ async getInitializeUserStatsIx(): Promise<TransactionInstruction> {
483
+ return await this.program.instruction.initializeUserStats({
484
+ accounts: {
485
+ userStats: this.getUserStatsAccountPublicKey(),
486
+ authority: this.wallet.publicKey,
487
+ payer: this.wallet.publicKey,
488
+ rent: anchor.web3.SYSVAR_RENT_PUBKEY,
489
+ systemProgram: anchor.web3.SystemProgram.programId,
490
+ state: await this.getStatePublicKey(),
491
+ },
492
+ });
493
+ }
494
+
495
+ public async updateUserName(
496
+ name: string,
497
+ userId = 0
498
+ ): Promise<TransactionSignature> {
499
+ const nameBuffer = encodeName(name);
500
+ return await this.program.rpc.updateUserName(userId, nameBuffer, {
501
+ accounts: {
502
+ user: await this.getUserAccountPublicKey(),
503
+ authority: this.wallet.publicKey,
504
+ },
505
+ });
506
+ }
507
+
508
+ public async updateUserCustomMarginRatio(
509
+ marginRatio: number,
510
+ userId = 0
511
+ ): Promise<TransactionSignature> {
512
+ return await this.program.rpc.updateUserCustomMarginRatio(
513
+ userId,
514
+ marginRatio,
515
+ {
516
+ accounts: {
517
+ user: await this.getUserAccountPublicKey(),
518
+ authority: this.wallet.publicKey,
519
+ },
520
+ }
521
+ );
522
+ }
523
+
524
+ public async updateUserDelegate(
525
+ delegate: PublicKey,
526
+ userId = 0
527
+ ): Promise<TransactionSignature> {
528
+ return await this.program.rpc.updateUserDelegate(userId, delegate, {
529
+ accounts: {
530
+ user: await this.getUserAccountPublicKey(),
531
+ authority: this.wallet.publicKey,
532
+ },
533
+ });
534
+ }
535
+
536
+ public async getUserAccountsForDelegate(
537
+ delegate: PublicKey
538
+ ): Promise<UserAccount[]> {
539
+ const programAccounts = await this.program.account.user.all([
540
+ {
541
+ memcmp: {
542
+ offset: 40,
543
+ /** data to match, as base-58 encoded string and limited to less than 129 bytes */
544
+ bytes: bs58.encode(delegate.toBuffer()),
545
+ },
546
+ },
547
+ ]);
548
+
549
+ return programAccounts.map(
550
+ (programAccount) => programAccount.account as UserAccount
551
+ );
552
+ }
553
+
554
+ public async deleteUser(userId = 0): Promise<TransactionSignature> {
555
+ const userAccountPublicKey = getUserAccountPublicKeySync(
556
+ this.program.programId,
557
+ this.wallet.publicKey,
558
+ userId
559
+ );
560
+
561
+ const txSig = await this.program.rpc.deleteUser({
562
+ accounts: {
563
+ user: userAccountPublicKey,
564
+ userStats: this.getUserStatsAccountPublicKey(),
565
+ authority: this.wallet.publicKey,
566
+ state: await this.getStatePublicKey(),
567
+ },
568
+ });
569
+
570
+ await this.users.get(userId)?.unsubscribe();
571
+ this.users.delete(userId);
572
+
573
+ return txSig;
574
+ }
575
+
351
576
  public getUser(userId?: number): ClearingHouseUser {
352
577
  userId = userId ?? this.activeUserId;
353
578
  if (!this.users.has(userId)) {
@@ -360,6 +585,23 @@ export class ClearingHouse {
360
585
  return [...this.users.values()];
361
586
  }
362
587
 
588
+ public getUserStats(): ClearingHouseUserStats {
589
+ return this.userStats;
590
+ }
591
+
592
+ userStatsAccountPublicKey: PublicKey;
593
+ public getUserStatsAccountPublicKey(): PublicKey {
594
+ if (this.userStatsAccountPublicKey) {
595
+ return this.userStatsAccountPublicKey;
596
+ }
597
+
598
+ this.userStatsAccountPublicKey = getUserStatsAccountPublicKey(
599
+ this.program.programId,
600
+ this.authority
601
+ );
602
+ return this.userStatsAccountPublicKey;
603
+ }
604
+
363
605
  public async getUserAccountPublicKey(): Promise<PublicKey> {
364
606
  return this.getUser().userAccountPublicKey;
365
607
  }
@@ -374,72 +616,76 @@ export class ClearingHouse {
374
616
  return this.getUser(userId).getUserAccountAndSlot();
375
617
  }
376
618
 
377
- public getUserBankBalance(
378
- bankIndex: number | BN
379
- ): UserBankBalance | undefined {
380
- const bankIndexBN = bankIndex instanceof BN ? bankIndex : new BN(bankIndex);
381
- return this.getUserAccount().bankBalances.find((bankBalance) =>
382
- bankBalance.bankIndex.eq(bankIndexBN)
619
+ public getSpotPosition(marketIndex: number): SpotPosition | undefined {
620
+ return this.getUserAccount().spotPositions.find(
621
+ (spotPosition) => spotPosition.marketIndex === marketIndex
383
622
  );
384
623
  }
385
624
 
386
625
  public getQuoteAssetTokenAmount(): BN {
387
- const bank = this.getBankAccount(QUOTE_ASSET_BANK_INDEX);
388
- const userBankBalance = this.getUserBankBalance(QUOTE_ASSET_BANK_INDEX);
626
+ const spotMarket = this.getSpotMarketAccount(QUOTE_SPOT_MARKET_INDEX);
627
+ const spotPosition = this.getSpotPosition(QUOTE_SPOT_MARKET_INDEX);
389
628
  return getTokenAmount(
390
- userBankBalance.balance,
391
- bank,
392
- userBankBalance.balanceType
629
+ spotPosition.balance,
630
+ spotMarket,
631
+ spotPosition.balanceType
393
632
  );
394
633
  }
395
634
 
396
- getRemainingAccounts(params: {
397
- writableMarketIndex?: BN;
398
- writableBankIndex?: BN;
399
- }): AccountMeta[] {
400
- const userAccountAndSlot = this.getUserAccountAndSlot();
401
- if (!userAccountAndSlot) {
402
- throw Error(
403
- 'No user account found. Most likely user account does not exist or failed to fetch account'
404
- );
405
- }
406
- const { data: userAccount, slot: lastUserPositionsSlot } =
407
- userAccountAndSlot;
635
+ getRemainingAccounts(params: RemainingAccountParams): AccountMeta[] {
636
+ const { oracleAccountMap, spotMarketAccountMap, perpMarketAccountMap } =
637
+ this.getRemainingAccountMapsForUsers(params.userAccounts);
408
638
 
409
- const oracleAccountMap = new Map<string, AccountMeta>();
410
- const bankAccountMap = new Map<number, AccountMeta>();
411
- const marketAccountMap = new Map<number, AccountMeta>();
412
- for (const [marketIndexNum, slot] of this.marketLastSlotCache.entries()) {
413
- // if cache has more recent slot than user positions account slot, add market to remaining accounts
414
- // otherwise remove from slot
415
- if (slot > lastUserPositionsSlot) {
416
- const marketAccount = this.getMarketAccount(marketIndexNum);
417
- marketAccountMap.set(marketIndexNum, {
418
- pubkey: marketAccount.pubkey,
419
- isSigner: false,
420
- isWritable: false,
421
- });
422
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
423
- pubkey: marketAccount.amm.oracle,
424
- isSigner: false,
425
- isWritable: false,
426
- });
427
- } else {
428
- this.marketLastSlotCache.delete(marketIndexNum);
639
+ if (params.useMarketLastSlotCache) {
640
+ const lastUserPositionsSlot = this.getUserAccountAndSlot()?.slot;
641
+ for (const [marketIndex, slot] of this.marketLastSlotCache.entries()) {
642
+ // if cache has more recent slot than user positions account slot, add market to remaining accounts
643
+ // otherwise remove from slot
644
+ if (slot > lastUserPositionsSlot) {
645
+ const marketAccount = this.getPerpMarketAccount(marketIndex);
646
+ perpMarketAccountMap.set(marketIndex, {
647
+ pubkey: marketAccount.pubkey,
648
+ isSigner: false,
649
+ isWritable: false,
650
+ });
651
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
652
+ pubkey: marketAccount.amm.oracle,
653
+ isSigner: false,
654
+ isWritable: false,
655
+ });
656
+ } else {
657
+ this.marketLastSlotCache.delete(marketIndex);
658
+ }
429
659
  }
430
660
  }
431
661
 
432
- for (const position of userAccount.positions) {
433
- if (!positionIsAvailable(position)) {
434
- const marketIndexNum = position.marketIndex.toNumber();
435
- const marketAccount = this.getMarketAccount(marketIndexNum);
436
- marketAccountMap.set(marketIndexNum, {
662
+ if (params.readablePerpMarketIndex !== undefined) {
663
+ const marketAccount = this.getPerpMarketAccount(
664
+ params.readablePerpMarketIndex
665
+ );
666
+ perpMarketAccountMap.set(params.readablePerpMarketIndex, {
667
+ pubkey: marketAccount.pubkey,
668
+ isSigner: false,
669
+ isWritable: false,
670
+ });
671
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
672
+ pubkey: marketAccount.amm.oracle,
673
+ isSigner: false,
674
+ isWritable: false,
675
+ });
676
+ }
677
+
678
+ if (params.writablePerpMarketIndexes !== undefined) {
679
+ for (const writablePerpMarketIndex of params.writablePerpMarketIndexes) {
680
+ const marketAccount = this.getPerpMarketAccount(
681
+ writablePerpMarketIndex
682
+ );
683
+ perpMarketAccountMap.set(writablePerpMarketIndex, {
437
684
  pubkey: marketAccount.pubkey,
438
685
  isSigner: false,
439
- // isWritable: false, // TODO
440
686
  isWritable: true,
441
687
  });
442
- oracleAccountMap.set(marketAccount.pubkey.toString(), {
688
+ oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
443
689
  pubkey: marketAccount.amm.oracle,
444
690
  isSigner: false,
445
691
  isWritable: false,
@@ -447,67 +693,108 @@ export class ClearingHouse {
447
693
  }
448
694
  }
449
695
 
450
- if (params.writableMarketIndex) {
451
- const marketAccount = this.getMarketAccount(
452
- params.writableMarketIndex.toNumber()
696
+ if (params.readableSpotMarketIndex !== undefined) {
697
+ const spotMarketAccount = this.getSpotMarketAccount(
698
+ params.readableSpotMarketIndex
453
699
  );
454
- marketAccountMap.set(params.writableMarketIndex.toNumber(), {
455
- pubkey: marketAccount.pubkey,
456
- isSigner: false,
457
- isWritable: true,
458
- });
459
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
460
- pubkey: marketAccount.amm.oracle,
700
+ spotMarketAccountMap.set(params.readableSpotMarketIndex, {
701
+ pubkey: spotMarketAccount.pubkey,
461
702
  isSigner: false,
462
703
  isWritable: false,
463
704
  });
464
- }
465
-
466
- for (const userBankBalance of userAccount.bankBalances) {
467
- if (!userBankBalance.balance.eq(ZERO)) {
468
- const bankAccount = this.getBankAccount(userBankBalance.bankIndex);
469
- bankAccountMap.set(userBankBalance.bankIndex.toNumber(), {
470
- pubkey: bankAccount.pubkey,
705
+ if (spotMarketAccount.marketIndex !== 0) {
706
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
707
+ pubkey: spotMarketAccount.oracle,
471
708
  isSigner: false,
472
709
  isWritable: false,
473
710
  });
474
- if (!bankAccount.bankIndex.eq(ZERO)) {
475
- oracleAccountMap.set(bankAccount.oracle.toString(), {
476
- pubkey: bankAccount.oracle,
477
- isSigner: false,
478
- isWritable: false,
479
- });
480
- }
481
711
  }
482
712
  }
483
713
 
484
- if (params.writableBankIndex) {
485
- const bankAccount = this.getBankAccount(params.writableBankIndex);
486
- bankAccountMap.set(params.writableBankIndex.toNumber(), {
487
- pubkey: bankAccount.pubkey,
488
- isSigner: false,
489
- isWritable: true,
490
- });
491
- if (!bankAccount.bankIndex.eq(ZERO)) {
492
- oracleAccountMap.set(bankAccount.oracle.toString(), {
493
- pubkey: bankAccount.oracle,
714
+ if (params.writableSpotMarketIndexes !== undefined) {
715
+ for (const writableSpotMarketIndex of params.writableSpotMarketIndexes) {
716
+ const spotMarketAccount = this.getSpotMarketAccount(
717
+ writableSpotMarketIndex
718
+ );
719
+ spotMarketAccountMap.set(spotMarketAccount.marketIndex, {
720
+ pubkey: spotMarketAccount.pubkey,
494
721
  isSigner: false,
495
- isWritable: false,
722
+ isWritable: true,
496
723
  });
724
+ if (!spotMarketAccount.oracle.equals(PublicKey.default)) {
725
+ oracleAccountMap.set(spotMarketAccount.oracle.toString(), {
726
+ pubkey: spotMarketAccount.oracle,
727
+ isSigner: false,
728
+ isWritable: false,
729
+ });
730
+ }
497
731
  }
498
732
  }
499
733
 
500
734
  return [
501
735
  ...oracleAccountMap.values(),
502
- ...bankAccountMap.values(),
503
- ...marketAccountMap.values(),
736
+ ...spotMarketAccountMap.values(),
737
+ ...perpMarketAccountMap.values(),
504
738
  ];
505
739
  }
506
740
 
507
- public getOrder(orderId: BN | number): Order | undefined {
508
- const orderIdBN = orderId instanceof BN ? orderId : new BN(orderId);
509
- return this.getUserAccount()?.orders.find((order) =>
510
- order.orderId.eq(orderIdBN)
741
+ getRemainingAccountMapsForUsers(userAccounts: UserAccount[]): {
742
+ oracleAccountMap: Map<string, AccountMeta>;
743
+ spotMarketAccountMap: Map<number, AccountMeta>;
744
+ perpMarketAccountMap: Map<number, AccountMeta>;
745
+ } {
746
+ const oracleAccountMap = new Map<string, AccountMeta>();
747
+ const spotMarketAccountMap = new Map<number, AccountMeta>();
748
+ const perpMarketAccountMap = new Map<number, AccountMeta>();
749
+
750
+ for (const userAccount of userAccounts) {
751
+ for (const spotPosition of userAccount.spotPositions) {
752
+ if (!isSpotPositionAvailable(spotPosition)) {
753
+ const spotMarket = this.getSpotMarketAccount(
754
+ spotPosition.marketIndex
755
+ );
756
+ spotMarketAccountMap.set(spotPosition.marketIndex, {
757
+ pubkey: spotMarket.pubkey,
758
+ isSigner: false,
759
+ isWritable: false,
760
+ });
761
+
762
+ if (!spotMarket.oracle.equals(PublicKey.default)) {
763
+ oracleAccountMap.set(spotMarket.oracle.toString(), {
764
+ pubkey: spotMarket.oracle,
765
+ isSigner: false,
766
+ isWritable: false,
767
+ });
768
+ }
769
+ }
770
+ }
771
+ for (const position of userAccount.perpPositions) {
772
+ if (!positionIsAvailable(position)) {
773
+ const market = this.getPerpMarketAccount(position.marketIndex);
774
+ perpMarketAccountMap.set(position.marketIndex, {
775
+ pubkey: market.pubkey,
776
+ isWritable: false,
777
+ isSigner: false,
778
+ });
779
+ oracleAccountMap.set(market.amm.oracle.toString(), {
780
+ pubkey: market.amm.oracle,
781
+ isWritable: false,
782
+ isSigner: false,
783
+ });
784
+ }
785
+ }
786
+ }
787
+
788
+ return {
789
+ oracleAccountMap,
790
+ spotMarketAccountMap,
791
+ perpMarketAccountMap,
792
+ };
793
+ }
794
+
795
+ public getOrder(orderId: number): Order | undefined {
796
+ return this.getUserAccount()?.orders.find(
797
+ (order) => order.orderId === orderId
511
798
  );
512
799
  }
513
800
 
@@ -519,29 +806,71 @@ export class ClearingHouse {
519
806
 
520
807
  public async deposit(
521
808
  amount: BN,
522
- bankIndex: BN,
809
+ marketIndex: number,
523
810
  collateralAccountPublicKey: PublicKey,
524
811
  userId?: number,
525
812
  reduceOnly = false
526
813
  ): Promise<TransactionSignature> {
814
+ const tx = new Transaction();
815
+ const additionalSigners: Array<Signer> = [];
816
+
817
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
818
+
819
+ const isSolMarket = spotMarketAccount.mint.equals(WRAPPED_SOL_MINT);
820
+
821
+ const authority = this.authority;
822
+
823
+ const createWSOLTokenAccount =
824
+ isSolMarket && collateralAccountPublicKey.equals(authority);
825
+
826
+ if (createWSOLTokenAccount) {
827
+ const { ixs, signers, pubkey } =
828
+ await this.getWrappedSolAccountCreationIxs(amount, true);
829
+
830
+ collateralAccountPublicKey = pubkey;
831
+
832
+ ixs.forEach((ix) => {
833
+ tx.add(ix);
834
+ });
835
+
836
+ signers.forEach((signer) => additionalSigners.push(signer));
837
+ }
838
+
527
839
  const depositCollateralIx = await this.getDepositInstruction(
528
840
  amount,
529
- bankIndex,
841
+ marketIndex,
530
842
  collateralAccountPublicKey,
531
843
  userId,
532
844
  reduceOnly,
533
845
  true
534
846
  );
535
847
 
536
- const tx = new Transaction().add(depositCollateralIx);
848
+ tx.add(depositCollateralIx);
849
+
850
+ // Close the wrapped sol account at the end of the transaction
851
+ if (createWSOLTokenAccount) {
852
+ tx.add(
853
+ Token.createCloseAccountInstruction(
854
+ TOKEN_PROGRAM_ID,
855
+ collateralAccountPublicKey,
856
+ authority,
857
+ authority,
858
+ []
859
+ )
860
+ );
861
+ }
537
862
 
538
- const { txSig } = await this.txSender.send(tx);
863
+ const { txSig } = await this.txSender.send(
864
+ tx,
865
+ additionalSigners,
866
+ this.opts
867
+ );
539
868
  return txSig;
540
869
  }
541
870
 
542
871
  async getDepositInstruction(
543
872
  amount: BN,
544
- bankIndex: BN,
873
+ marketIndex: number,
545
874
  userTokenAccount: PublicKey,
546
875
  userId?: number,
547
876
  reduceOnly = false,
@@ -550,7 +879,7 @@ export class ClearingHouse {
550
879
  const userAccountPublicKey = userId
551
880
  ? await getUserAccountPublicKey(
552
881
  this.program.programId,
553
- this.wallet.publicKey,
882
+ this.authority,
554
883
  userId
555
884
  )
556
885
  : await this.getUserAccountPublicKey();
@@ -558,30 +887,30 @@ export class ClearingHouse {
558
887
  let remainingAccounts = [];
559
888
  if (userInitialized) {
560
889
  remainingAccounts = this.getRemainingAccounts({
561
- writableBankIndex: bankIndex,
890
+ userAccounts: [this.getUserAccount()],
891
+ useMarketLastSlotCache: true,
892
+ writableSpotMarketIndexes: [marketIndex],
562
893
  });
563
894
  } else {
564
- remainingAccounts = [
565
- {
566
- pubkey: this.getBankAccount(bankIndex).pubkey,
567
- isSigner: false,
568
- isWritable: true,
569
- },
570
- ];
895
+ remainingAccounts = this.getRemainingAccounts({
896
+ userAccounts: [],
897
+ writableSpotMarketIndexes: [marketIndex],
898
+ });
571
899
  }
572
900
 
573
- const bank = this.getBankAccount(bankIndex);
901
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
574
902
 
575
903
  return await this.program.instruction.deposit(
576
- bankIndex,
904
+ marketIndex,
577
905
  amount,
578
906
  reduceOnly,
579
907
  {
580
908
  accounts: {
581
909
  state: await this.getStatePublicKey(),
582
- bank: bank.pubkey,
583
- bankVault: bank.vault,
910
+ spotMarket: spotMarketAccount.pubkey,
911
+ spotMarketVault: spotMarketAccount.vault,
584
912
  user: userAccountPublicKey,
913
+ userStats: this.getUserStatsAccountPublicKey(),
585
914
  userTokenAccount: userTokenAccount,
586
915
  authority: this.wallet.publicKey,
587
916
  tokenProgram: TOKEN_PROGRAM_ID,
@@ -591,43 +920,172 @@ export class ClearingHouse {
591
920
  );
592
921
  }
593
922
 
923
+ private async checkIfAccountExists(account: PublicKey): Promise<boolean> {
924
+ try {
925
+ const accountInfo = await this.connection.getAccountInfo(account);
926
+ return accountInfo != null;
927
+ } catch (e) {
928
+ // Doesn't already exist
929
+ return false;
930
+ }
931
+ }
932
+
933
+ private async getWrappedSolAccountCreationIxs(
934
+ amount: BN,
935
+ isDeposit?: boolean
936
+ ): Promise<{
937
+ ixs: anchor.web3.TransactionInstruction[];
938
+ signers: Signer[];
939
+ pubkey: PublicKey;
940
+ }> {
941
+ const wrappedSolAccount = new Keypair();
942
+
943
+ const result = {
944
+ ixs: [],
945
+ signers: [],
946
+ pubkey: wrappedSolAccount.publicKey,
947
+ };
948
+
949
+ const rentSpaceLamports = new BN(LAMPORTS_PER_SOL / 100);
950
+
951
+ const lamports = isDeposit
952
+ ? amount.add(rentSpaceLamports)
953
+ : rentSpaceLamports;
954
+
955
+ const authority = this.wallet.publicKey;
956
+
957
+ result.ixs.push(
958
+ SystemProgram.createAccount({
959
+ fromPubkey: authority,
960
+ newAccountPubkey: wrappedSolAccount.publicKey,
961
+ lamports: lamports.toNumber(),
962
+ space: 165,
963
+ programId: TOKEN_PROGRAM_ID,
964
+ })
965
+ );
966
+
967
+ result.ixs.push(
968
+ Token.createInitAccountInstruction(
969
+ TOKEN_PROGRAM_ID,
970
+ WRAPPED_SOL_MINT,
971
+ wrappedSolAccount.publicKey,
972
+ authority
973
+ )
974
+ );
975
+
976
+ result.signers.push(wrappedSolAccount);
977
+
978
+ return result;
979
+ }
980
+
981
+ public getAssociatedTokenAccountCreationIx(
982
+ tokenMintAddress: PublicKey,
983
+ associatedTokenAddress: PublicKey
984
+ ): anchor.web3.TransactionInstruction {
985
+ const createAssociatedAccountIx =
986
+ Token.createAssociatedTokenAccountInstruction(
987
+ ASSOCIATED_TOKEN_PROGRAM_ID,
988
+ TOKEN_PROGRAM_ID,
989
+ tokenMintAddress,
990
+ associatedTokenAddress,
991
+ this.wallet.publicKey,
992
+ this.wallet.publicKey
993
+ );
994
+
995
+ return createAssociatedAccountIx;
996
+ }
997
+
594
998
  /**
595
999
  * Creates the Clearing House User account for a user, and deposits some initial collateral
596
- * @param userId
597
- * @param name
598
1000
  * @param amount
599
1001
  * @param userTokenAccount
1002
+ * @param marketIndex
1003
+ * @param userId
1004
+ * @param name
600
1005
  * @param fromUserId
601
1006
  * @returns
602
1007
  */
603
1008
  public async initializeUserAccountAndDepositCollateral(
604
1009
  amount: BN,
605
1010
  userTokenAccount: PublicKey,
606
- bankIndex = new BN(0),
1011
+ marketIndex = 0,
607
1012
  userId = 0,
608
1013
  name = DEFAULT_USER_NAME,
609
- fromUserId?: number
1014
+ fromUserId?: number,
1015
+ referrerInfo?: ReferrerInfo
610
1016
  ): Promise<[TransactionSignature, PublicKey]> {
611
1017
  const [userAccountPublicKey, initializeUserAccountIx] =
612
- await this.getInitializeUserInstructions(userId, name);
1018
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
1019
+
1020
+ const additionalSigners: Array<Signer> = [];
1021
+
1022
+ const spotMarket = this.getSpotMarketAccount(marketIndex);
1023
+
1024
+ const isSolMarket = spotMarket.mint.equals(WRAPPED_SOL_MINT);
1025
+
1026
+ const tx = new Transaction();
1027
+
1028
+ const authority = this.wallet.publicKey;
1029
+
1030
+ const createWSOLTokenAccount =
1031
+ isSolMarket && userTokenAccount.equals(authority);
1032
+
1033
+ if (createWSOLTokenAccount) {
1034
+ const {
1035
+ ixs: startIxs,
1036
+ signers,
1037
+ pubkey,
1038
+ } = await this.getWrappedSolAccountCreationIxs(amount, true);
1039
+
1040
+ userTokenAccount = pubkey;
1041
+
1042
+ startIxs.forEach((ix) => {
1043
+ tx.add(ix);
1044
+ });
1045
+
1046
+ signers.forEach((signer) => additionalSigners.push(signer));
1047
+ }
613
1048
 
614
1049
  const depositCollateralIx =
615
1050
  fromUserId != null
616
- ? await this.getTransferDepositIx(amount, bankIndex, fromUserId, userId)
1051
+ ? await this.getTransferDepositIx(
1052
+ amount,
1053
+ marketIndex,
1054
+ fromUserId,
1055
+ userId
1056
+ )
617
1057
  : await this.getDepositInstruction(
618
1058
  amount,
619
- bankIndex,
1059
+ marketIndex,
620
1060
  userTokenAccount,
621
1061
  userId,
622
1062
  false,
623
1063
  false
624
1064
  );
625
1065
 
626
- const tx = new Transaction()
627
- .add(initializeUserAccountIx)
628
- .add(depositCollateralIx);
1066
+ if (userId === 0) {
1067
+ tx.add(await this.getInitializeUserStatsIx());
1068
+ }
1069
+ tx.add(initializeUserAccountIx).add(depositCollateralIx);
1070
+
1071
+ // Close the wrapped sol account at the end of the transaction
1072
+ if (createWSOLTokenAccount) {
1073
+ tx.add(
1074
+ Token.createCloseAccountInstruction(
1075
+ TOKEN_PROGRAM_ID,
1076
+ userTokenAccount,
1077
+ authority,
1078
+ authority,
1079
+ []
1080
+ )
1081
+ );
1082
+ }
629
1083
 
630
- const { txSig } = await this.txSender.send(tx, []);
1084
+ const { txSig } = await this.txSender.send(
1085
+ tx,
1086
+ additionalSigners,
1087
+ this.opts
1088
+ );
631
1089
 
632
1090
  return [txSig, userAccountPublicKey];
633
1091
  }
@@ -635,54 +1093,109 @@ export class ClearingHouse {
635
1093
  public async initializeUserAccountForDevnet(
636
1094
  userId = 0,
637
1095
  name = DEFAULT_USER_NAME,
638
- mockUSDCFaucet: MockUSDCFaucet,
639
- amount: BN
1096
+ marketIndex: number,
1097
+ tokenFaucet: TokenFaucet,
1098
+ amount: BN,
1099
+ referrerInfo?: ReferrerInfo
640
1100
  ): Promise<[TransactionSignature, PublicKey]> {
641
1101
  const [associateTokenPublicKey, createAssociatedAccountIx, mintToIx] =
642
- await mockUSDCFaucet.createAssociatedTokenAccountAndMintToInstructions(
1102
+ await tokenFaucet.createAssociatedTokenAccountAndMintToInstructions(
643
1103
  this.wallet.publicKey,
644
1104
  amount
645
1105
  );
646
1106
 
647
1107
  const [userAccountPublicKey, initializeUserAccountIx] =
648
- await this.getInitializeUserInstructions(userId, name);
1108
+ await this.getInitializeUserInstructions(userId, name, referrerInfo);
649
1109
 
650
1110
  const depositCollateralIx = await this.getDepositInstruction(
651
1111
  amount,
652
- new BN(0),
1112
+ marketIndex,
653
1113
  associateTokenPublicKey,
654
1114
  userId,
655
1115
  false,
656
1116
  false
657
1117
  );
658
1118
 
659
- const tx = new Transaction()
660
- .add(createAssociatedAccountIx)
661
- .add(mintToIx)
662
- .add(initializeUserAccountIx)
663
- .add(depositCollateralIx);
1119
+ const tx = new Transaction().add(createAssociatedAccountIx).add(mintToIx);
664
1120
 
665
- const txSig = await this.program.provider.sendAndConfirm(tx, []);
1121
+ if (userId === 0) {
1122
+ tx.add(await this.getInitializeUserStatsIx());
1123
+ }
1124
+ tx.add(initializeUserAccountIx).add(depositCollateralIx);
1125
+
1126
+ const txSig = await this.program.provider.sendAndConfirm(tx, []);
666
1127
 
667
1128
  return [txSig, userAccountPublicKey];
668
1129
  }
669
1130
 
670
1131
  public async withdraw(
671
1132
  amount: BN,
672
- bankIndex: BN,
1133
+ marketIndex: number,
673
1134
  userTokenAccount: PublicKey,
674
1135
  reduceOnly = false
675
1136
  ): Promise<TransactionSignature> {
676
- const { txSig } = await this.txSender.send(
677
- wrapInTx(
678
- await this.getWithdrawIx(
679
- amount,
680
- bankIndex,
1137
+ const tx = new Transaction();
1138
+ const additionalSigners: Array<Signer> = [];
1139
+
1140
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
1141
+
1142
+ const isSolMarket = spotMarketAccount.mint.equals(WRAPPED_SOL_MINT);
1143
+
1144
+ const authority = this.wallet.publicKey;
1145
+
1146
+ const createWSOLTokenAccount =
1147
+ isSolMarket && userTokenAccount.equals(authority);
1148
+
1149
+ if (createWSOLTokenAccount) {
1150
+ const { ixs, signers, pubkey } =
1151
+ await this.getWrappedSolAccountCreationIxs(amount, false);
1152
+
1153
+ userTokenAccount = pubkey;
1154
+
1155
+ ixs.forEach((ix) => {
1156
+ tx.add(ix);
1157
+ });
1158
+
1159
+ signers.forEach((signer) => additionalSigners.push(signer));
1160
+ } else {
1161
+ const accountExists = await this.checkIfAccountExists(userTokenAccount);
1162
+
1163
+ if (!accountExists) {
1164
+ const createAssociatedTokenAccountIx =
1165
+ this.getAssociatedTokenAccountCreationIx(
1166
+ spotMarketAccount.mint,
1167
+ userTokenAccount
1168
+ );
1169
+
1170
+ tx.add(createAssociatedTokenAccountIx);
1171
+ }
1172
+ }
1173
+
1174
+ const withdrawCollateral = await this.getWithdrawIx(
1175
+ amount,
1176
+ spotMarketAccount.marketIndex,
1177
+ userTokenAccount,
1178
+ reduceOnly
1179
+ );
1180
+
1181
+ tx.add(withdrawCollateral);
1182
+
1183
+ // Close the wrapped sol account at the end of the transaction
1184
+ if (createWSOLTokenAccount) {
1185
+ tx.add(
1186
+ Token.createCloseAccountInstruction(
1187
+ TOKEN_PROGRAM_ID,
681
1188
  userTokenAccount,
682
- reduceOnly
1189
+ authority,
1190
+ authority,
1191
+ []
683
1192
  )
684
- ),
685
- [],
1193
+ );
1194
+ }
1195
+
1196
+ const { txSig } = await this.txSender.send(
1197
+ tx,
1198
+ additionalSigners,
686
1199
  this.opts
687
1200
  );
688
1201
  return txSig;
@@ -690,29 +1203,32 @@ export class ClearingHouse {
690
1203
 
691
1204
  public async getWithdrawIx(
692
1205
  amount: BN,
693
- bankIndex: BN,
1206
+ marketIndex: number,
694
1207
  userTokenAccount: PublicKey,
695
1208
  reduceOnly = false
696
1209
  ): Promise<TransactionInstruction> {
697
1210
  const userAccountPublicKey = await this.getUserAccountPublicKey();
698
1211
 
699
1212
  const remainingAccounts = this.getRemainingAccounts({
700
- writableBankIndex: bankIndex,
1213
+ userAccounts: [this.getUserAccount()],
1214
+ useMarketLastSlotCache: true,
1215
+ writableSpotMarketIndexes: [marketIndex],
701
1216
  });
702
1217
 
703
- const bank = this.getBankAccount(bankIndex);
1218
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
704
1219
 
705
1220
  return await this.program.instruction.withdraw(
706
- bankIndex,
1221
+ marketIndex,
707
1222
  amount,
708
1223
  reduceOnly,
709
1224
  {
710
1225
  accounts: {
711
1226
  state: await this.getStatePublicKey(),
712
- bank: bank.pubkey,
713
- bankVault: bank.vault,
714
- bankVaultAuthority: bank.vaultAuthority,
1227
+ spotMarket: spotMarketAccount.pubkey,
1228
+ spotMarketVault: spotMarketAccount.vault,
1229
+ clearingHouseSigner: this.getSignerPublicKey(),
715
1230
  user: userAccountPublicKey,
1231
+ userStats: this.getUserStatsAccountPublicKey(),
716
1232
  userTokenAccount: userTokenAccount,
717
1233
  authority: this.wallet.publicKey,
718
1234
  tokenProgram: TOKEN_PROGRAM_ID,
@@ -724,13 +1240,18 @@ export class ClearingHouse {
724
1240
 
725
1241
  public async transferDeposit(
726
1242
  amount: BN,
727
- bankIndex: BN,
1243
+ marketIndex: number,
728
1244
  fromUserId: number,
729
1245
  toUserId: number
730
1246
  ): Promise<TransactionSignature> {
731
1247
  const { txSig } = await this.txSender.send(
732
1248
  wrapInTx(
733
- await this.getTransferDepositIx(amount, bankIndex, fromUserId, toUserId)
1249
+ await this.getTransferDepositIx(
1250
+ amount,
1251
+ marketIndex,
1252
+ fromUserId,
1253
+ toUserId
1254
+ )
734
1255
  ),
735
1256
  [],
736
1257
  this.opts
@@ -740,7 +1261,7 @@ export class ClearingHouse {
740
1261
 
741
1262
  public async getTransferDepositIx(
742
1263
  amount: BN,
743
- bankIndex: BN,
1264
+ marketIndex: number,
744
1265
  fromUserId: number,
745
1266
  toUserId: number
746
1267
  ): Promise<TransactionInstruction> {
@@ -755,93 +1276,292 @@ export class ClearingHouse {
755
1276
  toUserId
756
1277
  );
757
1278
 
758
- const remainingAccounts = this.getRemainingAccounts({
759
- writableBankIndex: bankIndex,
760
- });
1279
+ let remainingAccounts;
1280
+ if (this.users.has(fromUserId)) {
1281
+ remainingAccounts = this.getRemainingAccounts({
1282
+ userAccounts: [this.users.get(fromUserId).getUserAccount()],
1283
+ useMarketLastSlotCache: true,
1284
+ writableSpotMarketIndexes: [marketIndex],
1285
+ });
1286
+ } else {
1287
+ const userAccountPublicKey = getUserAccountPublicKeySync(
1288
+ this.program.programId,
1289
+ this.authority,
1290
+ fromUserId
1291
+ );
1292
+
1293
+ const fromUserAccount = (await this.program.account.user.fetch(
1294
+ userAccountPublicKey
1295
+ )) as UserAccount;
1296
+ remainingAccounts = this.getRemainingAccounts({
1297
+ userAccounts: [fromUserAccount],
1298
+ useMarketLastSlotCache: true,
1299
+ writableSpotMarketIndexes: [marketIndex],
1300
+ });
1301
+ }
761
1302
 
762
- return await this.program.instruction.transferDeposit(bankIndex, amount, {
1303
+ return await this.program.instruction.transferDeposit(marketIndex, amount, {
763
1304
  accounts: {
764
1305
  authority: this.wallet.publicKey,
765
1306
  fromUser,
766
1307
  toUser,
1308
+ userStats: this.getUserStatsAccountPublicKey(),
767
1309
  state: await this.getStatePublicKey(),
768
1310
  },
769
1311
  remainingAccounts,
770
1312
  });
771
1313
  }
772
1314
 
773
- public async updateBankCumulativeInterest(
774
- bankIndex: BN
1315
+ public async updateSpotMarketCumulativeInterest(
1316
+ marketIndex: number
1317
+ ): Promise<TransactionSignature> {
1318
+ const { txSig } = await this.txSender.send(
1319
+ wrapInTx(await this.updateSpotMarketCumulativeInterestIx(marketIndex)),
1320
+ [],
1321
+ this.opts
1322
+ );
1323
+ return txSig;
1324
+ }
1325
+
1326
+ public async updateSpotMarketCumulativeInterestIx(
1327
+ marketIndex: number
1328
+ ): Promise<TransactionInstruction> {
1329
+ const spotMarket = this.getSpotMarketAccount(marketIndex);
1330
+ return await this.program.instruction.updateSpotMarketCumulativeInterest({
1331
+ accounts: {
1332
+ state: await this.getStatePublicKey(),
1333
+ spotMarket: spotMarket.pubkey,
1334
+ },
1335
+ });
1336
+ }
1337
+
1338
+ public async settleLP(
1339
+ settleeUserAccountPublicKey: PublicKey,
1340
+ marketIndex: number
1341
+ ): Promise<TransactionSignature> {
1342
+ const { txSig } = await this.txSender.send(
1343
+ wrapInTx(await this.settleLPIx(settleeUserAccountPublicKey, marketIndex)),
1344
+ [],
1345
+ this.opts
1346
+ );
1347
+ return txSig;
1348
+ }
1349
+
1350
+ public async settleLPIx(
1351
+ settleeUserAccountPublicKey: PublicKey,
1352
+ marketIndex: number
1353
+ ): Promise<TransactionInstruction> {
1354
+ const settleeUserAccount = (await this.program.account.user.fetch(
1355
+ settleeUserAccountPublicKey
1356
+ )) as UserAccount;
1357
+
1358
+ const remainingAccounts = this.getRemainingAccounts({
1359
+ userAccounts: [settleeUserAccount],
1360
+ writablePerpMarketIndexes: [marketIndex],
1361
+ });
1362
+
1363
+ return this.program.instruction.settleLp(marketIndex, {
1364
+ accounts: {
1365
+ state: await this.getStatePublicKey(),
1366
+ user: settleeUserAccountPublicKey,
1367
+ },
1368
+ remainingAccounts: remainingAccounts,
1369
+ });
1370
+ }
1371
+
1372
+ public async removeLiquidity(
1373
+ marketIndex: number,
1374
+ sharesToBurn?: BN
775
1375
  ): Promise<TransactionSignature> {
776
1376
  const { txSig } = await this.txSender.send(
777
- wrapInTx(await this.updateBankCumulativeInterestIx(bankIndex)),
1377
+ wrapInTx(await this.getRemoveLiquidityIx(marketIndex, sharesToBurn)),
1378
+ [],
1379
+ this.opts
1380
+ );
1381
+ return txSig;
1382
+ }
1383
+
1384
+ public async getRemoveLiquidityIx(
1385
+ marketIndex: number,
1386
+ sharesToBurn?: BN
1387
+ ): Promise<TransactionInstruction> {
1388
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1389
+
1390
+ const remainingAccounts = this.getRemainingAccounts({
1391
+ userAccounts: [this.getUserAccount()],
1392
+ useMarketLastSlotCache: true,
1393
+ writablePerpMarketIndexes: [marketIndex],
1394
+ });
1395
+
1396
+ if (sharesToBurn == undefined) {
1397
+ const userAccount = this.getUserAccount();
1398
+ const perpPosition = userAccount.perpPositions.filter(
1399
+ (position) => position.marketIndex === marketIndex
1400
+ )[0];
1401
+ sharesToBurn = perpPosition.lpShares;
1402
+ console.log('burning lp shares:', sharesToBurn.toString());
1403
+ }
1404
+
1405
+ return this.program.instruction.removeLiquidity(sharesToBurn, marketIndex, {
1406
+ accounts: {
1407
+ state: await this.getStatePublicKey(),
1408
+ user: userAccountPublicKey,
1409
+ authority: this.wallet.publicKey,
1410
+ },
1411
+ remainingAccounts: remainingAccounts,
1412
+ });
1413
+ }
1414
+
1415
+ public async addLiquidity(
1416
+ amount: BN,
1417
+ marketIndex: number
1418
+ ): Promise<TransactionSignature> {
1419
+ const { txSig, slot } = await this.txSender.send(
1420
+ wrapInTx(await this.getAddLiquidityIx(amount, marketIndex)),
778
1421
  [],
779
1422
  this.opts
780
1423
  );
1424
+ this.marketLastSlotCache.set(marketIndex, slot);
781
1425
  return txSig;
782
1426
  }
783
1427
 
784
- public async updateBankCumulativeInterestIx(
785
- bankIndex: BN
1428
+ public async getAddLiquidityIx(
1429
+ amount: BN,
1430
+ marketIndex: number
786
1431
  ): Promise<TransactionInstruction> {
787
- const bank = this.getBankAccount(bankIndex);
788
- return await this.program.instruction.updateBankCumulativeInterest({
1432
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1433
+ const remainingAccounts = this.getRemainingAccounts({
1434
+ userAccounts: [this.getUserAccount()],
1435
+ useMarketLastSlotCache: true,
1436
+ writablePerpMarketIndexes: [marketIndex],
1437
+ });
1438
+
1439
+ return this.program.instruction.addLiquidity(amount, marketIndex, {
789
1440
  accounts: {
790
- bank: bank.pubkey,
1441
+ state: await this.getStatePublicKey(),
1442
+ user: userAccountPublicKey,
1443
+ authority: this.wallet.publicKey,
791
1444
  },
1445
+ remainingAccounts: remainingAccounts,
792
1446
  });
793
1447
  }
794
1448
 
795
1449
  public async openPosition(
796
1450
  direction: PositionDirection,
797
1451
  amount: BN,
798
- marketIndex: BN,
1452
+ marketIndex: number,
799
1453
  limitPrice?: BN
800
1454
  ): Promise<TransactionSignature> {
801
- return await this.placeAndTake(
802
- getMarketOrderParams(
1455
+ return await this.placeAndTake({
1456
+ orderType: OrderType.MARKET,
1457
+ marketIndex,
1458
+ direction,
1459
+ baseAssetAmount: amount,
1460
+ price: limitPrice,
1461
+ });
1462
+ }
1463
+
1464
+ public async sendSignedTx(tx: Transaction): Promise<TransactionSignature> {
1465
+ const { txSig } = await this.txSender.send(tx, undefined, this.opts, true);
1466
+
1467
+ return txSig;
1468
+ }
1469
+
1470
+ /**
1471
+ * Sends a market order and returns a signed tx which can fill the order against the vamm, which the caller can use to fill their own order if required.
1472
+ * @param orderParams
1473
+ * @param userAccountPublicKey
1474
+ * @param userAccount
1475
+ * @returns
1476
+ */
1477
+ public async sendMarketOrderAndGetSignedFillTx(
1478
+ orderParams: OptionalOrderParams,
1479
+ userAccountPublicKey: PublicKey,
1480
+ userAccount: UserAccount
1481
+ ): Promise<{ txSig: TransactionSignature; signedFillTx: Transaction }> {
1482
+ const marketIndex = orderParams.marketIndex;
1483
+ const orderId = userAccount.nextOrderId;
1484
+
1485
+ const marketOrderTx = wrapInTx(await this.getPlaceOrderIx(orderParams));
1486
+ const fillTx = wrapInTx(
1487
+ await this.getFillOrderIx(userAccountPublicKey, userAccount, {
1488
+ orderId,
803
1489
  marketIndex,
804
- direction,
805
- ZERO,
806
- amount,
807
- false,
808
- limitPrice
809
- )
1490
+ })
1491
+ );
1492
+
1493
+ // Apply the latest blockhash to the txs so that we can sign before sending them
1494
+ const currentBlockHash = (
1495
+ await this.connection.getLatestBlockhash('finalized')
1496
+ ).blockhash;
1497
+ marketOrderTx.recentBlockhash = currentBlockHash;
1498
+ fillTx.recentBlockhash = currentBlockHash;
1499
+
1500
+ marketOrderTx.feePayer = userAccount.authority;
1501
+ fillTx.feePayer = userAccount.authority;
1502
+
1503
+ const [signedMarketOrderTx, signedFillTx] =
1504
+ await this.provider.wallet.signAllTransactions([marketOrderTx, fillTx]);
1505
+
1506
+ const { txSig, slot } = await this.txSender.send(
1507
+ signedMarketOrderTx,
1508
+ [],
1509
+ this.opts,
1510
+ true
810
1511
  );
1512
+
1513
+ this.marketLastSlotCache.set(orderParams.marketIndex, slot);
1514
+
1515
+ return { txSig, signedFillTx };
811
1516
  }
812
1517
 
813
1518
  public async placeOrder(
814
- orderParams: OrderParams
1519
+ orderParams: OptionalOrderParams
815
1520
  ): Promise<TransactionSignature> {
816
1521
  const { txSig, slot } = await this.txSender.send(
817
1522
  wrapInTx(await this.getPlaceOrderIx(orderParams)),
818
1523
  [],
819
1524
  this.opts
820
1525
  );
821
- this.marketLastSlotCache.set(orderParams.marketIndex.toNumber(), slot);
1526
+ this.marketLastSlotCache.set(orderParams.marketIndex, slot);
822
1527
  return txSig;
823
1528
  }
824
1529
 
1530
+ getOrderParams(
1531
+ optionalOrderParams: OptionalOrderParams,
1532
+ marketType: MarketType
1533
+ ): OrderParams {
1534
+ return Object.assign({}, DefaultOrderParams, optionalOrderParams, {
1535
+ marketType,
1536
+ });
1537
+ }
1538
+
825
1539
  public async getPlaceOrderIx(
826
- orderParams: OrderParams
1540
+ orderParams: OptionalOrderParams
827
1541
  ): Promise<TransactionInstruction> {
1542
+ orderParams = this.getOrderParams(orderParams, MarketType.PERP);
828
1543
  const userAccountPublicKey = await this.getUserAccountPublicKey();
829
1544
 
830
1545
  const remainingAccounts = this.getRemainingAccounts({
831
- writableMarketIndex: orderParams.marketIndex,
1546
+ userAccounts: [this.getUserAccount()],
1547
+ useMarketLastSlotCache: true,
1548
+ readablePerpMarketIndex: orderParams.marketIndex,
832
1549
  });
833
1550
 
834
1551
  return await this.program.instruction.placeOrder(orderParams, {
835
1552
  accounts: {
836
1553
  state: await this.getStatePublicKey(),
837
1554
  user: userAccountPublicKey,
1555
+ userStats: this.getUserStatsAccountPublicKey(),
838
1556
  authority: this.wallet.publicKey,
839
1557
  },
840
1558
  remainingAccounts,
841
1559
  });
842
1560
  }
843
1561
 
844
- public async updateAMMs(marketIndexes: BN[]): Promise<TransactionSignature> {
1562
+ public async updateAMMs(
1563
+ marketIndexes: number[]
1564
+ ): Promise<TransactionSignature> {
845
1565
  const { txSig } = await this.txSender.send(
846
1566
  wrapInTx(await this.getUpdateAMMsIx(marketIndexes)),
847
1567
  [],
@@ -851,16 +1571,16 @@ export class ClearingHouse {
851
1571
  }
852
1572
 
853
1573
  public async getUpdateAMMsIx(
854
- marketIndexes: BN[]
1574
+ marketIndexes: number[]
855
1575
  ): Promise<TransactionInstruction> {
856
1576
  for (let i = marketIndexes.length; i < 5; i++) {
857
- marketIndexes.push(new BN(100));
1577
+ marketIndexes.push(100);
858
1578
  }
859
1579
  const marketAccountInfos = [];
860
1580
  const oracleAccountInfos = [];
861
1581
  for (const marketIndex of marketIndexes) {
862
- if (!marketIndex.eq(new BN(100))) {
863
- const market = this.getMarketAccount(marketIndex);
1582
+ if (marketIndex !== 100) {
1583
+ const market = this.getPerpMarketAccount(marketIndex);
864
1584
  marketAccountInfos.push({
865
1585
  pubkey: market.pubkey,
866
1586
  isWritable: true,
@@ -884,7 +1604,55 @@ export class ClearingHouse {
884
1604
  });
885
1605
  }
886
1606
 
887
- public async cancelOrder(orderId?: BN): Promise<TransactionSignature> {
1607
+ public async settleExpiredMarket(
1608
+ marketIndex: number
1609
+ ): Promise<TransactionSignature> {
1610
+ const { txSig } = await this.txSender.send(
1611
+ wrapInTx(await this.getSettleExpiredMarketIx(marketIndex)),
1612
+ [],
1613
+ this.opts
1614
+ );
1615
+ return txSig;
1616
+ }
1617
+
1618
+ public async getSettleExpiredMarketIx(
1619
+ marketIndex: number
1620
+ ): Promise<TransactionInstruction> {
1621
+ const marketAccountInfos = [];
1622
+ const oracleAccountInfos = [];
1623
+ const spotMarketAccountInfos = [];
1624
+ const market = this.getPerpMarketAccount(marketIndex);
1625
+ marketAccountInfos.push({
1626
+ pubkey: market.pubkey,
1627
+ isWritable: true,
1628
+ isSigner: false,
1629
+ });
1630
+ oracleAccountInfos.push({
1631
+ pubkey: market.amm.oracle,
1632
+ isWritable: false,
1633
+ isSigner: false,
1634
+ });
1635
+
1636
+ spotMarketAccountInfos.push({
1637
+ pubkey: this.getSpotMarketAccount(QUOTE_SPOT_MARKET_INDEX).pubkey,
1638
+ isSigner: false,
1639
+ isWritable: true,
1640
+ });
1641
+
1642
+ const remainingAccounts = oracleAccountInfos
1643
+ .concat(spotMarketAccountInfos)
1644
+ .concat(marketAccountInfos);
1645
+
1646
+ return await this.program.instruction.settleExpiredMarket(marketIndex, {
1647
+ accounts: {
1648
+ state: await this.getStatePublicKey(),
1649
+ authority: this.wallet.publicKey,
1650
+ },
1651
+ remainingAccounts,
1652
+ });
1653
+ }
1654
+
1655
+ public async cancelOrder(orderId?: number): Promise<TransactionSignature> {
888
1656
  const { txSig } = await this.txSender.send(
889
1657
  wrapInTx(await this.getCancelOrderIx(orderId)),
890
1658
  [],
@@ -893,10 +1661,15 @@ export class ClearingHouse {
893
1661
  return txSig;
894
1662
  }
895
1663
 
896
- public async getCancelOrderIx(orderId?: BN): Promise<TransactionInstruction> {
1664
+ public async getCancelOrderIx(
1665
+ orderId?: number
1666
+ ): Promise<TransactionInstruction> {
897
1667
  const userAccountPublicKey = await this.getUserAccountPublicKey();
898
1668
 
899
- const remainingAccounts = this.getRemainingAccounts({});
1669
+ const remainingAccounts = this.getRemainingAccounts({
1670
+ userAccounts: [this.getUserAccount()],
1671
+ useMarketLastSlotCache: true,
1672
+ });
900
1673
 
901
1674
  return await this.program.instruction.cancelOrder(orderId ?? null, {
902
1675
  accounts: {
@@ -925,9 +1698,12 @@ export class ClearingHouse {
925
1698
  const userAccountPublicKey = await this.getUserAccountPublicKey();
926
1699
 
927
1700
  const order = this.getOrderByUserId(userOrderId);
928
- const oracle = this.getMarketAccount(order.marketIndex).amm.oracle;
1701
+ const oracle = this.getPerpMarketAccount(order.marketIndex).amm.oracle;
929
1702
 
930
- const remainingAccounts = this.getRemainingAccounts({});
1703
+ const remainingAccounts = this.getRemainingAccounts({
1704
+ userAccounts: [this.getUserAccount()],
1705
+ useMarketLastSlotCache: true,
1706
+ });
931
1707
 
932
1708
  return await this.program.instruction.cancelOrderByUserId(userOrderId, {
933
1709
  accounts: {
@@ -943,12 +1719,19 @@ export class ClearingHouse {
943
1719
  public async fillOrder(
944
1720
  userAccountPublicKey: PublicKey,
945
1721
  user: UserAccount,
946
- order?: Order,
947
- makerInfo?: MakerInfo
1722
+ order?: Pick<Order, 'marketIndex' | 'orderId'>,
1723
+ makerInfo?: MakerInfo,
1724
+ referrerInfo?: ReferrerInfo
948
1725
  ): Promise<TransactionSignature> {
949
1726
  const { txSig } = await this.txSender.send(
950
1727
  wrapInTx(
951
- await this.getFillOrderIx(userAccountPublicKey, user, order, makerInfo)
1728
+ await this.getFillOrderIx(
1729
+ userAccountPublicKey,
1730
+ user,
1731
+ order,
1732
+ makerInfo,
1733
+ referrerInfo
1734
+ )
952
1735
  ),
953
1736
  [],
954
1737
  this.opts
@@ -959,60 +1742,54 @@ export class ClearingHouse {
959
1742
  public async getFillOrderIx(
960
1743
  userAccountPublicKey: PublicKey,
961
1744
  userAccount: UserAccount,
962
- order: Order,
963
- makerInfo?: MakerInfo
1745
+ order: Pick<Order, 'marketIndex' | 'orderId'>,
1746
+ makerInfo?: MakerInfo,
1747
+ referrerInfo?: ReferrerInfo
964
1748
  ): Promise<TransactionInstruction> {
1749
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
1750
+ this.program.programId,
1751
+ userAccount.authority
1752
+ );
1753
+
965
1754
  const fillerPublicKey = await this.getUserAccountPublicKey();
1755
+ const fillerStatsPublicKey = this.getUserStatsAccountPublicKey();
966
1756
 
967
- const marketIndex = order.marketIndex;
968
- const marketAccount = this.getMarketAccount(marketIndex);
1757
+ const marketIndex = order
1758
+ ? order.marketIndex
1759
+ : userAccount.orders.find(
1760
+ (order) => order.orderId === userAccount.nextOrderId - 1
1761
+ ).marketIndex;
969
1762
 
970
- const bankAccountInfos = [
971
- {
972
- pubkey: this.getQuoteAssetBankAccount().pubkey,
973
- isSigner: false,
974
- isWritable: true,
975
- },
976
- ];
977
- const marketAccountInfos = [
978
- {
979
- pubkey: marketAccount.pubkey,
1763
+ const userAccounts = [userAccount];
1764
+ if (makerInfo !== undefined) {
1765
+ userAccounts.push(makerInfo.makerUserAccount);
1766
+ }
1767
+ const remainingAccounts = this.getRemainingAccounts({
1768
+ userAccounts,
1769
+ writablePerpMarketIndexes: [marketIndex],
1770
+ });
1771
+
1772
+ if (makerInfo) {
1773
+ remainingAccounts.push({
1774
+ pubkey: makerInfo.maker,
980
1775
  isWritable: true,
981
1776
  isSigner: false,
982
- },
983
- ];
984
- const oracleAccountInfos = [
985
- {
986
- pubkey: marketAccount.amm.oracle,
987
- isWritable: false,
1777
+ });
1778
+ remainingAccounts.push({
1779
+ pubkey: makerInfo.makerStats,
1780
+ isWritable: true,
988
1781
  isSigner: false,
989
- },
990
- ];
991
- for (const position of userAccount.positions) {
992
- if (
993
- !positionIsAvailable(position) &&
994
- !position.marketIndex.eq(order.marketIndex)
995
- ) {
996
- const market = this.getMarketAccount(position.marketIndex);
997
- marketAccountInfos.push({
998
- pubkey: market.pubkey,
999
- isWritable: false,
1000
- isSigner: false,
1001
- });
1002
- oracleAccountInfos.push({
1003
- pubkey: market.amm.oracle,
1004
- isWritable: false,
1005
- isSigner: false,
1006
- });
1007
- }
1782
+ });
1008
1783
  }
1009
- const remainingAccounts = oracleAccountInfos.concat(
1010
- bankAccountInfos.concat(marketAccountInfos)
1011
- );
1012
1784
 
1013
- if (makerInfo) {
1785
+ if (referrerInfo) {
1014
1786
  remainingAccounts.push({
1015
- pubkey: makerInfo.maker,
1787
+ pubkey: referrerInfo.referrer,
1788
+ isWritable: true,
1789
+ isSigner: false,
1790
+ });
1791
+ remainingAccounts.push({
1792
+ pubkey: referrerInfo.referrerStats,
1016
1793
  isWritable: true,
1017
1794
  isSigner: false,
1018
1795
  });
@@ -1020,81 +1797,279 @@ export class ClearingHouse {
1020
1797
 
1021
1798
  const orderId = order.orderId;
1022
1799
  const makerOrderId = makerInfo ? makerInfo.order.orderId : null;
1800
+
1023
1801
  return await this.program.instruction.fillOrder(orderId, makerOrderId, {
1024
1802
  accounts: {
1025
1803
  state: await this.getStatePublicKey(),
1026
1804
  filler: fillerPublicKey,
1805
+ fillerStats: fillerStatsPublicKey,
1027
1806
  user: userAccountPublicKey,
1807
+ userStats: userStatsPublicKey,
1028
1808
  authority: this.wallet.publicKey,
1029
1809
  },
1030
1810
  remainingAccounts,
1031
1811
  });
1032
1812
  }
1033
1813
 
1034
- public async triggerOrder(
1035
- userAccountPublicKey: PublicKey,
1036
- user: UserAccount,
1037
- order: Order
1814
+ public async placeSpotOrder(
1815
+ orderParams: OptionalOrderParams
1038
1816
  ): Promise<TransactionSignature> {
1039
1817
  const { txSig } = await this.txSender.send(
1040
- wrapInTx(await this.getTriggerOrderIx(userAccountPublicKey, user, order)),
1818
+ wrapInTx(await this.getPlaceSpotOrderIx(orderParams)),
1041
1819
  [],
1042
1820
  this.opts
1043
1821
  );
1044
1822
  return txSig;
1045
1823
  }
1046
1824
 
1047
- public async getTriggerOrderIx(
1048
- userAccountPublicKey: PublicKey,
1049
- userAccount: UserAccount,
1050
- order: Order
1825
+ public async getPlaceSpotOrderIx(
1826
+ orderParams: OptionalOrderParams
1051
1827
  ): Promise<TransactionInstruction> {
1052
- const fillerPublicKey = await this.getUserAccountPublicKey();
1828
+ orderParams = this.getOrderParams(orderParams, MarketType.SPOT);
1829
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
1053
1830
 
1054
- const marketIndex = order.marketIndex;
1055
- const marketAccount = this.getMarketAccount(marketIndex);
1831
+ const remainingAccounts = this.getRemainingAccounts({
1832
+ userAccounts: [this.getUserAccount()],
1833
+ useMarketLastSlotCache: true,
1834
+ readableSpotMarketIndex: orderParams.marketIndex,
1835
+ });
1056
1836
 
1057
- const bankAccountInfos = [
1058
- {
1059
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1060
- isSigner: false,
1061
- isWritable: true,
1062
- },
1063
- ];
1064
- const marketAccountInfos = [
1065
- {
1066
- pubkey: marketAccount.pubkey,
1067
- isWritable: true,
1068
- isSigner: false,
1837
+ return await this.program.instruction.placeSpotOrder(orderParams, {
1838
+ accounts: {
1839
+ state: await this.getStatePublicKey(),
1840
+ user: userAccountPublicKey,
1841
+ userStats: this.getUserStatsAccountPublicKey(),
1842
+ authority: this.wallet.publicKey,
1069
1843
  },
1070
- ];
1071
- const oracleAccountInfos = [
1072
- {
1073
- pubkey: marketAccount.amm.oracle,
1844
+ remainingAccounts,
1845
+ });
1846
+ }
1847
+
1848
+ public async fillSpotOrder(
1849
+ userAccountPublicKey: PublicKey,
1850
+ user: UserAccount,
1851
+ order?: Order,
1852
+ fulfillmentConfig?: SerumV3FulfillmentConfigAccount,
1853
+ makerInfo?: MakerInfo,
1854
+ referrerInfo?: ReferrerInfo
1855
+ ): Promise<TransactionSignature> {
1856
+ const { txSig } = await this.txSender.send(
1857
+ wrapInTx(
1858
+ await this.getFillSpotOrderIx(
1859
+ userAccountPublicKey,
1860
+ user,
1861
+ order,
1862
+ fulfillmentConfig,
1863
+ makerInfo,
1864
+ referrerInfo
1865
+ )
1866
+ ),
1867
+ [],
1868
+ this.opts
1869
+ );
1870
+ return txSig;
1871
+ }
1872
+
1873
+ public async getFillSpotOrderIx(
1874
+ userAccountPublicKey: PublicKey,
1875
+ userAccount: UserAccount,
1876
+ order?: Order,
1877
+ fulfillmentConfig?: SerumV3FulfillmentConfigAccount,
1878
+ makerInfo?: MakerInfo,
1879
+ referrerInfo?: ReferrerInfo
1880
+ ): Promise<TransactionInstruction> {
1881
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
1882
+ this.program.programId,
1883
+ userAccount.authority
1884
+ );
1885
+
1886
+ const fillerPublicKey = await this.getUserAccountPublicKey();
1887
+ const fillerStatsPublicKey = this.getUserStatsAccountPublicKey();
1888
+
1889
+ const marketIndex = order
1890
+ ? order.marketIndex
1891
+ : userAccount.orders.find(
1892
+ (order) => order.orderId === userAccount.nextOrderId - 1
1893
+ ).marketIndex;
1894
+
1895
+ const userAccounts = [userAccount];
1896
+ if (makerInfo !== undefined) {
1897
+ userAccounts.push(makerInfo.makerUserAccount);
1898
+ }
1899
+ const remainingAccounts = this.getRemainingAccounts({
1900
+ userAccounts,
1901
+ writableSpotMarketIndexes: [marketIndex, QUOTE_SPOT_MARKET_INDEX],
1902
+ });
1903
+
1904
+ if (makerInfo) {
1905
+ remainingAccounts.push({
1906
+ pubkey: makerInfo.maker,
1907
+ isWritable: true,
1908
+ isSigner: false,
1909
+ });
1910
+ remainingAccounts.push({
1911
+ pubkey: makerInfo.makerStats,
1912
+ isWritable: true,
1913
+ isSigner: false,
1914
+ });
1915
+ }
1916
+
1917
+ if (referrerInfo) {
1918
+ remainingAccounts.push({
1919
+ pubkey: referrerInfo.referrer,
1920
+ isWritable: true,
1921
+ isSigner: false,
1922
+ });
1923
+ remainingAccounts.push({
1924
+ pubkey: referrerInfo.referrerStats,
1925
+ isWritable: true,
1926
+ isSigner: false,
1927
+ });
1928
+ }
1929
+
1930
+ const orderId = order.orderId;
1931
+ const makerOrderId = makerInfo ? makerInfo.order.orderId : null;
1932
+
1933
+ if (fulfillmentConfig) {
1934
+ remainingAccounts.push({
1935
+ pubkey: fulfillmentConfig.pubkey,
1074
1936
  isWritable: false,
1075
1937
  isSigner: false,
1076
- },
1077
- ];
1078
- for (const position of userAccount.positions) {
1079
- if (
1080
- !positionIsAvailable(position) &&
1081
- !position.marketIndex.eq(order.marketIndex)
1082
- ) {
1083
- const market = this.getMarketAccount(position.marketIndex);
1084
- marketAccountInfos.push({
1085
- pubkey: market.pubkey,
1086
- isWritable: false,
1087
- isSigner: false,
1088
- });
1089
- oracleAccountInfos.push({
1090
- pubkey: market.amm.oracle,
1091
- isWritable: false,
1092
- isSigner: false,
1093
- });
1938
+ });
1939
+ remainingAccounts.push({
1940
+ pubkey: fulfillmentConfig.serumProgramId,
1941
+ isWritable: false,
1942
+ isSigner: false,
1943
+ });
1944
+ remainingAccounts.push({
1945
+ pubkey: fulfillmentConfig.serumMarket,
1946
+ isWritable: true,
1947
+ isSigner: false,
1948
+ });
1949
+ remainingAccounts.push({
1950
+ pubkey: fulfillmentConfig.serumRequestQueue,
1951
+ isWritable: true,
1952
+ isSigner: false,
1953
+ });
1954
+ remainingAccounts.push({
1955
+ pubkey: fulfillmentConfig.serumEventQueue,
1956
+ isWritable: true,
1957
+ isSigner: false,
1958
+ });
1959
+ remainingAccounts.push({
1960
+ pubkey: fulfillmentConfig.serumBids,
1961
+ isWritable: true,
1962
+ isSigner: false,
1963
+ });
1964
+ remainingAccounts.push({
1965
+ pubkey: fulfillmentConfig.serumAsks,
1966
+ isWritable: true,
1967
+ isSigner: false,
1968
+ });
1969
+ remainingAccounts.push({
1970
+ pubkey: fulfillmentConfig.serumBaseVault,
1971
+ isWritable: true,
1972
+ isSigner: false,
1973
+ });
1974
+ remainingAccounts.push({
1975
+ pubkey: fulfillmentConfig.serumQuoteVault,
1976
+ isWritable: true,
1977
+ isSigner: false,
1978
+ });
1979
+ remainingAccounts.push({
1980
+ pubkey: fulfillmentConfig.serumOpenOrders,
1981
+ isWritable: true,
1982
+ isSigner: false,
1983
+ });
1984
+ remainingAccounts.push({
1985
+ pubkey: getSerumSignerPublicKey(
1986
+ fulfillmentConfig.serumProgramId,
1987
+ fulfillmentConfig.serumMarket,
1988
+ fulfillmentConfig.serumSignerNonce
1989
+ ),
1990
+ isWritable: false,
1991
+ isSigner: false,
1992
+ });
1993
+ remainingAccounts.push({
1994
+ pubkey: this.getSignerPublicKey(),
1995
+ isWritable: false,
1996
+ isSigner: false,
1997
+ });
1998
+ remainingAccounts.push({
1999
+ pubkey: TOKEN_PROGRAM_ID,
2000
+ isWritable: false,
2001
+ isSigner: false,
2002
+ });
2003
+ remainingAccounts.push({
2004
+ pubkey: this.getSpotMarketAccount(marketIndex).vault,
2005
+ isWritable: true,
2006
+ isSigner: false,
2007
+ });
2008
+ remainingAccounts.push({
2009
+ pubkey: this.getQuoteSpotMarketAccount().vault,
2010
+ isWritable: true,
2011
+ isSigner: false,
2012
+ });
2013
+ remainingAccounts.push({
2014
+ pubkey: this.getStateAccount().srmVault,
2015
+ isWritable: false,
2016
+ isSigner: false,
2017
+ });
2018
+ }
2019
+
2020
+ return await this.program.instruction.fillSpotOrder(
2021
+ orderId,
2022
+ fulfillmentConfig ? fulfillmentConfig.fulfillmentType : null,
2023
+ makerOrderId,
2024
+ {
2025
+ accounts: {
2026
+ state: await this.getStatePublicKey(),
2027
+ filler: fillerPublicKey,
2028
+ fillerStats: fillerStatsPublicKey,
2029
+ user: userAccountPublicKey,
2030
+ userStats: userStatsPublicKey,
2031
+ authority: this.wallet.publicKey,
2032
+ },
2033
+ remainingAccounts,
1094
2034
  }
2035
+ );
2036
+ }
2037
+
2038
+ public async triggerOrder(
2039
+ userAccountPublicKey: PublicKey,
2040
+ user: UserAccount,
2041
+ order: Order
2042
+ ): Promise<TransactionSignature> {
2043
+ const { txSig } = await this.txSender.send(
2044
+ wrapInTx(await this.getTriggerOrderIx(userAccountPublicKey, user, order)),
2045
+ [],
2046
+ this.opts
2047
+ );
2048
+ return txSig;
2049
+ }
2050
+
2051
+ public async getTriggerOrderIx(
2052
+ userAccountPublicKey: PublicKey,
2053
+ userAccount: UserAccount,
2054
+ order: Order
2055
+ ): Promise<TransactionInstruction> {
2056
+ const fillerPublicKey = await this.getUserAccountPublicKey();
2057
+
2058
+ let remainingAccountsParams;
2059
+ if (isVariant(order.marketType, 'perp')) {
2060
+ remainingAccountsParams = {
2061
+ userAccounts: [userAccount],
2062
+ writablePerpMarketIndexes: [order.marketIndex],
2063
+ };
2064
+ } else {
2065
+ remainingAccountsParams = {
2066
+ userAccounts: [userAccount],
2067
+ writableSpotMarketIndexes: [order.marketIndex, QUOTE_SPOT_MARKET_INDEX],
2068
+ };
1095
2069
  }
1096
- const remainingAccounts = oracleAccountInfos.concat(
1097
- bankAccountInfos.concat(marketAccountInfos)
2070
+
2071
+ const remainingAccounts = this.getRemainingAccounts(
2072
+ remainingAccountsParams
1098
2073
  );
1099
2074
 
1100
2075
  const orderId = order.orderId;
@@ -1110,27 +2085,38 @@ export class ClearingHouse {
1110
2085
  }
1111
2086
 
1112
2087
  public async placeAndTake(
1113
- orderParams: OrderParams,
1114
- makerInfo?: MakerInfo
2088
+ orderParams: OptionalOrderParams,
2089
+ makerInfo?: MakerInfo,
2090
+ referrerInfo?: ReferrerInfo
1115
2091
  ): Promise<TransactionSignature> {
1116
2092
  const { txSig, slot } = await this.txSender.send(
1117
- wrapInTx(await this.getPlaceAndTakeIx(orderParams, makerInfo)),
2093
+ wrapInTx(
2094
+ await this.getPlaceAndTakeIx(orderParams, makerInfo, referrerInfo)
2095
+ ),
1118
2096
  [],
1119
2097
  this.opts
1120
2098
  );
1121
- this.marketLastSlotCache.set(orderParams.marketIndex.toNumber(), slot);
2099
+ this.marketLastSlotCache.set(orderParams.marketIndex, slot);
1122
2100
  return txSig;
1123
2101
  }
1124
2102
 
1125
2103
  public async getPlaceAndTakeIx(
1126
- orderParams: OrderParams,
1127
- makerInfo?: MakerInfo
2104
+ orderParams: OptionalOrderParams,
2105
+ makerInfo?: MakerInfo,
2106
+ referrerInfo?: ReferrerInfo
1128
2107
  ): Promise<TransactionInstruction> {
2108
+ orderParams = this.getOrderParams(orderParams, MarketType.PERP);
2109
+ const userStatsPublicKey = await this.getUserStatsAccountPublicKey();
1129
2110
  const userAccountPublicKey = await this.getUserAccountPublicKey();
1130
2111
 
2112
+ const userAccounts = [this.getUserAccount()];
2113
+ if (makerInfo !== undefined) {
2114
+ userAccounts.push(makerInfo.makerUserAccount);
2115
+ }
1131
2116
  const remainingAccounts = this.getRemainingAccounts({
1132
- writableMarketIndex: orderParams.marketIndex,
1133
- writableBankIndex: QUOTE_ASSET_BANK_INDEX,
2117
+ userAccounts,
2118
+ useMarketLastSlotCache: true,
2119
+ writablePerpMarketIndexes: [orderParams.marketIndex],
1134
2120
  });
1135
2121
 
1136
2122
  let makerOrderId = null;
@@ -1141,6 +2127,24 @@ export class ClearingHouse {
1141
2127
  isSigner: false,
1142
2128
  isWritable: true,
1143
2129
  });
2130
+ remainingAccounts.push({
2131
+ pubkey: makerInfo.makerStats,
2132
+ isSigner: false,
2133
+ isWritable: true,
2134
+ });
2135
+ }
2136
+
2137
+ if (referrerInfo) {
2138
+ remainingAccounts.push({
2139
+ pubkey: referrerInfo.referrer,
2140
+ isWritable: true,
2141
+ isSigner: false,
2142
+ });
2143
+ remainingAccounts.push({
2144
+ pubkey: referrerInfo.referrerStats,
2145
+ isWritable: true,
2146
+ isSigner: false,
2147
+ });
1144
2148
  }
1145
2149
 
1146
2150
  return await this.program.instruction.placeAndTake(
@@ -1150,6 +2154,71 @@ export class ClearingHouse {
1150
2154
  accounts: {
1151
2155
  state: await this.getStatePublicKey(),
1152
2156
  user: userAccountPublicKey,
2157
+ userStats: userStatsPublicKey,
2158
+ authority: this.wallet.publicKey,
2159
+ },
2160
+ remainingAccounts,
2161
+ }
2162
+ );
2163
+ }
2164
+
2165
+ public async placeAndMake(
2166
+ orderParams: OptionalOrderParams,
2167
+ takerInfo: TakerInfo,
2168
+ referrerInfo?: ReferrerInfo
2169
+ ): Promise<TransactionSignature> {
2170
+ const { txSig, slot } = await this.txSender.send(
2171
+ wrapInTx(
2172
+ await this.getPlaceAndMakeIx(orderParams, takerInfo, referrerInfo)
2173
+ ),
2174
+ [],
2175
+ this.opts
2176
+ );
2177
+
2178
+ this.marketLastSlotCache.set(orderParams.marketIndex, slot);
2179
+
2180
+ return txSig;
2181
+ }
2182
+
2183
+ public async getPlaceAndMakeIx(
2184
+ orderParams: OptionalOrderParams,
2185
+ takerInfo: TakerInfo,
2186
+ referrerInfo?: ReferrerInfo
2187
+ ): Promise<TransactionInstruction> {
2188
+ orderParams = this.getOrderParams(orderParams, MarketType.PERP);
2189
+ const userStatsPublicKey = this.getUserStatsAccountPublicKey();
2190
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
2191
+
2192
+ const remainingAccounts = this.getRemainingAccounts({
2193
+ userAccounts: [this.getUserAccount(), takerInfo.takerUserAccount],
2194
+ useMarketLastSlotCache: true,
2195
+ writablePerpMarketIndexes: [orderParams.marketIndex],
2196
+ });
2197
+
2198
+ if (referrerInfo) {
2199
+ remainingAccounts.push({
2200
+ pubkey: referrerInfo.referrer,
2201
+ isWritable: true,
2202
+ isSigner: false,
2203
+ });
2204
+ remainingAccounts.push({
2205
+ pubkey: referrerInfo.referrerStats,
2206
+ isWritable: true,
2207
+ isSigner: false,
2208
+ });
2209
+ }
2210
+
2211
+ const takerOrderId = takerInfo.order.orderId;
2212
+ return await this.program.instruction.placeAndMake(
2213
+ orderParams,
2214
+ takerOrderId,
2215
+ {
2216
+ accounts: {
2217
+ state: await this.getStatePublicKey(),
2218
+ user: userAccountPublicKey,
2219
+ userStats: userStatsPublicKey,
2220
+ taker: takerInfo.taker,
2221
+ takerStats: takerInfo.takerStats,
1153
2222
  authority: this.wallet.publicKey,
1154
2223
  },
1155
2224
  remainingAccounts,
@@ -1162,22 +2231,23 @@ export class ClearingHouse {
1162
2231
  * @param marketIndex
1163
2232
  * @returns
1164
2233
  */
1165
- public async closePosition(marketIndex: BN): Promise<TransactionSignature> {
2234
+ public async closePosition(
2235
+ marketIndex: number,
2236
+ limitPrice?: BN
2237
+ ): Promise<TransactionSignature> {
1166
2238
  const userPosition = this.getUser().getUserPosition(marketIndex);
1167
2239
  if (!userPosition) {
1168
2240
  throw Error(`No position in market ${marketIndex.toString()}`);
1169
2241
  }
1170
2242
 
1171
- return await this.placeAndTake(
1172
- getMarketOrderParams(
1173
- marketIndex,
1174
- findDirectionToClose(userPosition),
1175
- ZERO,
1176
- userPosition.baseAssetAmount,
1177
- true,
1178
- undefined
1179
- )
1180
- );
2243
+ return await this.placeAndTake({
2244
+ orderType: OrderType.MARKET,
2245
+ marketIndex,
2246
+ direction: findDirectionToClose(userPosition),
2247
+ baseAssetAmount: userPosition.baseAssetAmount.abs(),
2248
+ reduceOnly: true,
2249
+ price: limitPrice,
2250
+ });
1181
2251
  }
1182
2252
 
1183
2253
  public async settlePNLs(
@@ -1185,7 +2255,7 @@ export class ClearingHouse {
1185
2255
  settleeUserAccountPublicKey: PublicKey;
1186
2256
  settleeUserAccount: UserAccount;
1187
2257
  }[],
1188
- marketIndex: BN
2258
+ marketIndex: number
1189
2259
  ): Promise<TransactionSignature> {
1190
2260
  const ixs = [];
1191
2261
  for (const { settleeUserAccountPublicKey, settleeUserAccount } of users) {
@@ -1198,7 +2268,14 @@ export class ClearingHouse {
1198
2268
  );
1199
2269
  }
1200
2270
 
1201
- const tx = new Transaction().add(...ixs);
2271
+ const tx = new Transaction()
2272
+ .add(
2273
+ ComputeBudgetProgram.requestUnits({
2274
+ units: 1_000_000,
2275
+ additionalFee: 0,
2276
+ })
2277
+ )
2278
+ .add(...ixs);
1202
2279
 
1203
2280
  const { txSig } = await this.txSender.send(tx, [], this.opts);
1204
2281
  return txSig;
@@ -1207,7 +2284,7 @@ export class ClearingHouse {
1207
2284
  public async settlePNL(
1208
2285
  settleeUserAccountPublicKey: PublicKey,
1209
2286
  settleeUserAccount: UserAccount,
1210
- marketIndex: BN
2287
+ marketIndex: number
1211
2288
  ): Promise<TransactionSignature> {
1212
2289
  const { txSig } = await this.txSender.send(
1213
2290
  wrapInTx(
@@ -1226,70 +2303,55 @@ export class ClearingHouse {
1226
2303
  public async settlePNLIx(
1227
2304
  settleeUserAccountPublicKey: PublicKey,
1228
2305
  settleeUserAccount: UserAccount,
1229
- marketIndex: BN
2306
+ marketIndex: number
1230
2307
  ): Promise<TransactionInstruction> {
1231
- const marketAccountMap = new Map<number, AccountMeta>();
1232
- const oracleAccountMap = new Map<string, AccountMeta>();
1233
- const bankAccountMap = new Map<number, AccountMeta>();
1234
- for (const position of settleeUserAccount.positions) {
1235
- if (!positionIsAvailable(position)) {
1236
- const market = this.getMarketAccount(position.marketIndex);
1237
- marketAccountMap.set(position.marketIndex.toNumber(), {
1238
- pubkey: market.pubkey,
1239
- isWritable: true, // TODO
1240
- isSigner: false,
1241
- });
1242
- oracleAccountMap.set(market.amm.oracle.toString(), {
1243
- pubkey: market.amm.oracle,
1244
- isWritable: false,
1245
- isSigner: false,
1246
- });
1247
- }
1248
- }
1249
-
1250
- for (const userBankBalance of settleeUserAccount.bankBalances) {
1251
- if (!userBankBalance.balance.eq(QUOTE_ASSET_BANK_INDEX)) {
1252
- const bankAccount = this.getBankAccount(userBankBalance.bankIndex);
1253
- bankAccountMap.set(userBankBalance.bankIndex.toNumber(), {
1254
- pubkey: bankAccount.pubkey,
1255
- isSigner: false,
1256
- isWritable: false,
1257
- });
1258
- if (!bankAccount.bankIndex.eq(ZERO)) {
1259
- oracleAccountMap.set(bankAccount.oracle.toString(), {
1260
- pubkey: bankAccount.oracle,
1261
- isSigner: false,
1262
- isWritable: false,
1263
- });
1264
- }
1265
- }
1266
- }
2308
+ const remainingAccounts = this.getRemainingAccounts({
2309
+ userAccounts: [settleeUserAccount],
2310
+ writablePerpMarketIndexes: [marketIndex],
2311
+ writableSpotMarketIndexes: [QUOTE_SPOT_MARKET_INDEX],
2312
+ });
1267
2313
 
1268
- const marketAccount = this.getMarketAccount(marketIndex.toNumber());
1269
- marketAccountMap.set(marketIndex.toNumber(), {
1270
- pubkey: marketAccount.pubkey,
1271
- isSigner: false,
1272
- isWritable: true,
1273
- });
1274
- oracleAccountMap.set(marketAccount.amm.oracle.toString(), {
1275
- pubkey: marketAccount.amm.oracle,
1276
- isSigner: false,
1277
- isWritable: false,
2314
+ return await this.program.instruction.settlePnl(marketIndex, {
2315
+ accounts: {
2316
+ state: await this.getStatePublicKey(),
2317
+ authority: this.wallet.publicKey,
2318
+ user: settleeUserAccountPublicKey,
2319
+ },
2320
+ remainingAccounts: remainingAccounts,
1278
2321
  });
2322
+ }
1279
2323
 
1280
- bankAccountMap.set(QUOTE_ASSET_BANK_INDEX.toNumber(), {
1281
- pubkey: this.getBankAccount(QUOTE_ASSET_BANK_INDEX).pubkey,
1282
- isSigner: false,
1283
- isWritable: true,
1284
- });
2324
+ public async settleExpiredPosition(
2325
+ settleeUserAccountPublicKey: PublicKey,
2326
+ settleeUserAccount: UserAccount,
2327
+ marketIndex: number
2328
+ ): Promise<TransactionSignature> {
2329
+ const { txSig } = await this.txSender.send(
2330
+ wrapInTx(
2331
+ await this.getSettleExpiredPositionIx(
2332
+ settleeUserAccountPublicKey,
2333
+ settleeUserAccount,
2334
+ marketIndex
2335
+ )
2336
+ ),
2337
+ [],
2338
+ this.opts
2339
+ );
2340
+ return txSig;
2341
+ }
1285
2342
 
1286
- const remainingAccounts = [
1287
- ...oracleAccountMap.values(),
1288
- ...bankAccountMap.values(),
1289
- ...marketAccountMap.values(),
1290
- ];
2343
+ public async getSettleExpiredPositionIx(
2344
+ settleeUserAccountPublicKey: PublicKey,
2345
+ settleeUserAccount: UserAccount,
2346
+ marketIndex: number
2347
+ ): Promise<TransactionInstruction> {
2348
+ const remainingAccounts = this.getRemainingAccounts({
2349
+ userAccounts: [settleeUserAccount],
2350
+ writablePerpMarketIndexes: [marketIndex],
2351
+ writableSpotMarketIndexes: [QUOTE_SPOT_MARKET_INDEX],
2352
+ });
1291
2353
 
1292
- return await this.program.instruction.settlePnl(marketIndex, {
2354
+ return await this.program.instruction.settleExpiredPosition(marketIndex, {
1293
2355
  accounts: {
1294
2356
  state: await this.getStatePublicKey(),
1295
2357
  authority: this.wallet.publicKey,
@@ -1299,68 +2361,364 @@ export class ClearingHouse {
1299
2361
  });
1300
2362
  }
1301
2363
 
1302
- public async liquidate(
1303
- liquidateeUserAccountPublicKey: PublicKey
2364
+ public async liquidatePerp(
2365
+ userAccountPublicKey: PublicKey,
2366
+ userAccount: UserAccount,
2367
+ marketIndex: number,
2368
+ maxBaseAssetAmount: BN
2369
+ ): Promise<TransactionSignature> {
2370
+ const { txSig, slot } = await this.txSender.send(
2371
+ wrapInTx(
2372
+ await this.getLiquidatePerpIx(
2373
+ userAccountPublicKey,
2374
+ userAccount,
2375
+ marketIndex,
2376
+ maxBaseAssetAmount
2377
+ )
2378
+ ),
2379
+ [],
2380
+ this.opts
2381
+ );
2382
+ this.marketLastSlotCache.set(marketIndex, slot);
2383
+ return txSig;
2384
+ }
2385
+
2386
+ public async getLiquidatePerpIx(
2387
+ userAccountPublicKey: PublicKey,
2388
+ userAccount: UserAccount,
2389
+ marketIndex: number,
2390
+ maxBaseAssetAmount: BN
2391
+ ): Promise<TransactionInstruction> {
2392
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2393
+ this.program.programId,
2394
+ userAccount.authority
2395
+ );
2396
+
2397
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2398
+ const liquidatorStatsPublicKey = this.getUserStatsAccountPublicKey();
2399
+
2400
+ const remainingAccounts = this.getRemainingAccounts({
2401
+ userAccounts: [this.getUserAccount(), userAccount],
2402
+ useMarketLastSlotCache: true,
2403
+ writablePerpMarketIndexes: [marketIndex],
2404
+ });
2405
+
2406
+ return await this.program.instruction.liquidatePerp(
2407
+ marketIndex,
2408
+ maxBaseAssetAmount,
2409
+ {
2410
+ accounts: {
2411
+ state: await this.getStatePublicKey(),
2412
+ authority: this.wallet.publicKey,
2413
+ user: userAccountPublicKey,
2414
+ userStats: userStatsPublicKey,
2415
+ liquidator: liquidatorPublicKey,
2416
+ liquidatorStats: liquidatorStatsPublicKey,
2417
+ },
2418
+ remainingAccounts: remainingAccounts,
2419
+ }
2420
+ );
2421
+ }
2422
+
2423
+ public async liquidateSpot(
2424
+ userAccountPublicKey: PublicKey,
2425
+ userAccount: UserAccount,
2426
+ assetMarketIndex: number,
2427
+ liabilityMarketIndex: number,
2428
+ maxLiabilityTransfer: BN
1304
2429
  ): Promise<TransactionSignature> {
1305
2430
  const { txSig } = await this.txSender.send(
1306
- wrapInTx(await this.getLiquidateIx(liquidateeUserAccountPublicKey)),
2431
+ wrapInTx(
2432
+ await this.getLiquidateSpotIx(
2433
+ userAccountPublicKey,
2434
+ userAccount,
2435
+ assetMarketIndex,
2436
+ liabilityMarketIndex,
2437
+ maxLiabilityTransfer
2438
+ )
2439
+ ),
1307
2440
  [],
1308
2441
  this.opts
1309
2442
  );
1310
2443
  return txSig;
1311
2444
  }
1312
2445
 
1313
- public async getLiquidateIx(
1314
- liquidateeUserAccountPublicKey: PublicKey
2446
+ public async getLiquidateSpotIx(
2447
+ userAccountPublicKey: PublicKey,
2448
+ userAccount: UserAccount,
2449
+ assetMarketIndex: number,
2450
+ liabilityMarketIndex: number,
2451
+ maxLiabilityTransfer: BN
1315
2452
  ): Promise<TransactionInstruction> {
1316
- const userAccountPublicKey = await this.getUserAccountPublicKey();
1317
- const liquidateeUserAccount = (await this.program.account.user.fetch(
1318
- liquidateeUserAccountPublicKey
1319
- )) as UserAccount;
2453
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2454
+ this.program.programId,
2455
+ userAccount.authority
2456
+ );
2457
+
2458
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2459
+ const liquidatorStatsPublicKey = await this.getUserStatsAccountPublicKey();
2460
+
2461
+ const remainingAccounts = this.getRemainingAccounts({
2462
+ userAccounts: [this.getUserAccount(), userAccount],
2463
+ useMarketLastSlotCache: true,
2464
+ writableSpotMarketIndexes: [liabilityMarketIndex, assetMarketIndex],
2465
+ });
1320
2466
 
1321
- const bankAccountInfos = [
2467
+ return await this.program.instruction.liquidateSpot(
2468
+ assetMarketIndex,
2469
+ liabilityMarketIndex,
2470
+ maxLiabilityTransfer,
1322
2471
  {
1323
- pubkey: this.getQuoteAssetBankAccount().pubkey,
1324
- isSigner: false,
1325
- isWritable: true,
1326
- },
1327
- ];
1328
- const marketAccountInfos = [];
1329
- const oracleAccountInfos = [];
1330
- for (const position of liquidateeUserAccount.positions) {
1331
- if (!positionIsAvailable(position)) {
1332
- const market = this.getMarketAccount(position.marketIndex);
1333
- const marketPublicKey = await getMarketPublicKey(
1334
- this.program.programId,
1335
- position.marketIndex
1336
- );
1337
- marketAccountInfos.push({
1338
- pubkey: marketPublicKey,
1339
- isWritable: true,
1340
- isSigner: false,
1341
- });
1342
- oracleAccountInfos.push({
1343
- pubkey: market.amm.oracle,
1344
- isWritable: false,
1345
- isSigner: false,
1346
- });
2472
+ accounts: {
2473
+ state: await this.getStatePublicKey(),
2474
+ authority: this.wallet.publicKey,
2475
+ user: userAccountPublicKey,
2476
+ userStats: userStatsPublicKey,
2477
+ liquidator: liquidatorPublicKey,
2478
+ liquidatorStats: liquidatorStatsPublicKey,
2479
+ },
2480
+ remainingAccounts: remainingAccounts,
1347
2481
  }
1348
- }
1349
- const remainingAccounts = oracleAccountInfos.concat(
1350
- bankAccountInfos.concat(marketAccountInfos)
1351
2482
  );
2483
+ }
1352
2484
 
1353
- const state = this.getStateAccount();
1354
- const quoteAssetBankAccount = this.getQuoteAssetBankAccount();
1355
- return await this.program.instruction.liquidate({
2485
+ public async liquidateBorrowForPerpPnl(
2486
+ userAccountPublicKey: PublicKey,
2487
+ userAccount: UserAccount,
2488
+ perpMarketIndex: number,
2489
+ liabilityMarketIndex: number,
2490
+ maxLiabilityTransfer: BN
2491
+ ): Promise<TransactionSignature> {
2492
+ const { txSig, slot } = await this.txSender.send(
2493
+ wrapInTx(
2494
+ await this.getLiquidateBorrowForPerpPnlIx(
2495
+ userAccountPublicKey,
2496
+ userAccount,
2497
+ perpMarketIndex,
2498
+ liabilityMarketIndex,
2499
+ maxLiabilityTransfer
2500
+ )
2501
+ ),
2502
+ [],
2503
+ this.opts
2504
+ );
2505
+ this.marketLastSlotCache.set(perpMarketIndex, slot);
2506
+ return txSig;
2507
+ }
2508
+
2509
+ public async getLiquidateBorrowForPerpPnlIx(
2510
+ userAccountPublicKey: PublicKey,
2511
+ userAccount: UserAccount,
2512
+ perpMarketIndex: number,
2513
+ liabilityMarketIndex: number,
2514
+ maxLiabilityTransfer: BN
2515
+ ): Promise<TransactionInstruction> {
2516
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2517
+ this.program.programId,
2518
+ userAccount.authority
2519
+ );
2520
+
2521
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2522
+ const liquidatorStatsPublicKey = await this.getUserStatsAccountPublicKey();
2523
+
2524
+ const remainingAccounts = this.getRemainingAccounts({
2525
+ userAccounts: [this.getUserAccount(), userAccount],
2526
+ writablePerpMarketIndexes: [perpMarketIndex],
2527
+ writableSpotMarketIndexes: [liabilityMarketIndex],
2528
+ });
2529
+
2530
+ return await this.program.instruction.liquidateBorrowForPerpPnl(
2531
+ perpMarketIndex,
2532
+ liabilityMarketIndex,
2533
+ maxLiabilityTransfer,
2534
+ {
2535
+ accounts: {
2536
+ state: await this.getStatePublicKey(),
2537
+ authority: this.wallet.publicKey,
2538
+ user: userAccountPublicKey,
2539
+ userStats: userStatsPublicKey,
2540
+ liquidator: liquidatorPublicKey,
2541
+ liquidatorStats: liquidatorStatsPublicKey,
2542
+ },
2543
+ remainingAccounts: remainingAccounts,
2544
+ }
2545
+ );
2546
+ }
2547
+
2548
+ public async liquidatePerpPnlForDeposit(
2549
+ userAccountPublicKey: PublicKey,
2550
+ userAccount: UserAccount,
2551
+ perpMarketIndex: number,
2552
+ assetMarketIndex: number,
2553
+ maxPnlTransfer: BN
2554
+ ): Promise<TransactionSignature> {
2555
+ const { txSig, slot } = await this.txSender.send(
2556
+ wrapInTx(
2557
+ await this.getLiquidatePerpPnlForDepositIx(
2558
+ userAccountPublicKey,
2559
+ userAccount,
2560
+ perpMarketIndex,
2561
+ assetMarketIndex,
2562
+ maxPnlTransfer
2563
+ )
2564
+ ),
2565
+ [],
2566
+ this.opts
2567
+ );
2568
+ this.marketLastSlotCache.set(perpMarketIndex, slot);
2569
+ return txSig;
2570
+ }
2571
+
2572
+ public async getLiquidatePerpPnlForDepositIx(
2573
+ userAccountPublicKey: PublicKey,
2574
+ userAccount: UserAccount,
2575
+ perpMarketIndex: number,
2576
+ assetMarketIndex: number,
2577
+ maxPnlTransfer: BN
2578
+ ): Promise<TransactionInstruction> {
2579
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2580
+ this.program.programId,
2581
+ userAccount.authority
2582
+ );
2583
+
2584
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2585
+ const liquidatorStatsPublicKey = await this.getUserStatsAccountPublicKey();
2586
+
2587
+ const remainingAccounts = this.getRemainingAccounts({
2588
+ userAccounts: [this.getUserAccount(), userAccount],
2589
+ writablePerpMarketIndexes: [perpMarketIndex],
2590
+ writableSpotMarketIndexes: [assetMarketIndex],
2591
+ });
2592
+
2593
+ return await this.program.instruction.liquidatePerpPnlForDeposit(
2594
+ perpMarketIndex,
2595
+ assetMarketIndex,
2596
+ maxPnlTransfer,
2597
+ {
2598
+ accounts: {
2599
+ state: await this.getStatePublicKey(),
2600
+ authority: this.wallet.publicKey,
2601
+ user: userAccountPublicKey,
2602
+ userStats: userStatsPublicKey,
2603
+ liquidator: liquidatorPublicKey,
2604
+ liquidatorStats: liquidatorStatsPublicKey,
2605
+ },
2606
+ remainingAccounts: remainingAccounts,
2607
+ }
2608
+ );
2609
+ }
2610
+
2611
+ public async resolvePerpBankruptcy(
2612
+ userAccountPublicKey: PublicKey,
2613
+ userAccount: UserAccount,
2614
+ marketIndex: number
2615
+ ): Promise<TransactionSignature> {
2616
+ const { txSig } = await this.txSender.send(
2617
+ wrapInTx(
2618
+ await this.getResolvePerpBankruptcyIx(
2619
+ userAccountPublicKey,
2620
+ userAccount,
2621
+ marketIndex
2622
+ )
2623
+ ),
2624
+ [],
2625
+ this.opts
2626
+ );
2627
+ return txSig;
2628
+ }
2629
+
2630
+ public async getResolvePerpBankruptcyIx(
2631
+ userAccountPublicKey: PublicKey,
2632
+ userAccount: UserAccount,
2633
+ marketIndex: number
2634
+ ): Promise<TransactionInstruction> {
2635
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2636
+ this.program.programId,
2637
+ userAccount.authority
2638
+ );
2639
+
2640
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2641
+ const liquidatorStatsPublicKey = await this.getUserStatsAccountPublicKey();
2642
+
2643
+ const remainingAccounts = this.getRemainingAccounts({
2644
+ userAccounts: [this.getUserAccount(), userAccount],
2645
+ writablePerpMarketIndexes: [marketIndex],
2646
+ writableSpotMarketIndexes: [QUOTE_SPOT_MARKET_INDEX],
2647
+ });
2648
+
2649
+ const spotMarket = this.getSpotMarketAccount(marketIndex);
2650
+
2651
+ return await this.program.instruction.resolvePerpBankruptcy(
2652
+ QUOTE_SPOT_MARKET_INDEX,
2653
+ marketIndex,
2654
+ {
2655
+ accounts: {
2656
+ state: await this.getStatePublicKey(),
2657
+ authority: this.wallet.publicKey,
2658
+ user: userAccountPublicKey,
2659
+ userStats: userStatsPublicKey,
2660
+ liquidator: liquidatorPublicKey,
2661
+ liquidatorStats: liquidatorStatsPublicKey,
2662
+ spotMarketVault: spotMarket.vault,
2663
+ insuranceFundVault: spotMarket.insuranceFundVault,
2664
+ clearingHouseSigner: this.getSignerPublicKey(),
2665
+ tokenProgram: TOKEN_PROGRAM_ID,
2666
+ },
2667
+ remainingAccounts: remainingAccounts,
2668
+ }
2669
+ );
2670
+ }
2671
+
2672
+ public async resolveSpotBankruptcy(
2673
+ userAccountPublicKey: PublicKey,
2674
+ userAccount: UserAccount,
2675
+ marketIndex: number
2676
+ ): Promise<TransactionSignature> {
2677
+ const { txSig } = await this.txSender.send(
2678
+ wrapInTx(
2679
+ await this.getResolveSpotBankruptcyIx(
2680
+ userAccountPublicKey,
2681
+ userAccount,
2682
+ marketIndex
2683
+ )
2684
+ ),
2685
+ [],
2686
+ this.opts
2687
+ );
2688
+ return txSig;
2689
+ }
2690
+
2691
+ public async getResolveSpotBankruptcyIx(
2692
+ userAccountPublicKey: PublicKey,
2693
+ userAccount: UserAccount,
2694
+ marketIndex: number
2695
+ ): Promise<TransactionInstruction> {
2696
+ const userStatsPublicKey = getUserStatsAccountPublicKey(
2697
+ this.program.programId,
2698
+ userAccount.authority
2699
+ );
2700
+
2701
+ const liquidatorPublicKey = await this.getUserAccountPublicKey();
2702
+ const liquidatorStatsPublicKey = await this.getUserStatsAccountPublicKey();
2703
+
2704
+ const remainingAccounts = this.getRemainingAccounts({
2705
+ userAccounts: [this.getUserAccount(), userAccount],
2706
+ writableSpotMarketIndexes: [marketIndex],
2707
+ });
2708
+
2709
+ const spotMarket = this.getSpotMarketAccount(marketIndex);
2710
+
2711
+ return await this.program.instruction.resolveSpotBankruptcy(marketIndex, {
1356
2712
  accounts: {
1357
2713
  state: await this.getStatePublicKey(),
1358
2714
  authority: this.wallet.publicKey,
1359
- user: liquidateeUserAccountPublicKey,
1360
- liquidator: userAccountPublicKey,
1361
- bankVault: quoteAssetBankAccount.vault,
1362
- bankVaultAuthority: quoteAssetBankAccount.vaultAuthority,
1363
- insuranceVault: state.insuranceVault,
2715
+ user: userAccountPublicKey,
2716
+ userStats: userStatsPublicKey,
2717
+ liquidatorStats: liquidatorStatsPublicKey,
2718
+ liquidator: liquidatorPublicKey,
2719
+ spotMarketVault: spotMarket.vault,
2720
+ insuranceFundVault: spotMarket.insuranceFundVault,
2721
+ clearingHouseSigner: this.getSignerPublicKey(),
1364
2722
  tokenProgram: TOKEN_PROGRAM_ID,
1365
2723
  },
1366
2724
  remainingAccounts: remainingAccounts,
@@ -1369,7 +2727,7 @@ export class ClearingHouse {
1369
2727
 
1370
2728
  public async updateFundingRate(
1371
2729
  oracle: PublicKey,
1372
- marketIndex: BN
2730
+ marketIndex: number
1373
2731
  ): Promise<TransactionSignature> {
1374
2732
  const { txSig } = await this.txSender.send(
1375
2733
  wrapInTx(await this.getUpdateFundingRateIx(oracle, marketIndex)),
@@ -1381,7 +2739,7 @@ export class ClearingHouse {
1381
2739
 
1382
2740
  public async getUpdateFundingRateIx(
1383
2741
  oracle: PublicKey,
1384
- marketIndex: BN
2742
+ marketIndex: number
1385
2743
  ): Promise<TransactionInstruction> {
1386
2744
  return await this.program.instruction.updateFundingRate(marketIndex, {
1387
2745
  accounts: {
@@ -1393,10 +2751,10 @@ export class ClearingHouse {
1393
2751
  }
1394
2752
 
1395
2753
  public async settleFundingPayment(
1396
- userAccount: PublicKey
2754
+ userAccountPublicKey: PublicKey
1397
2755
  ): Promise<TransactionSignature> {
1398
2756
  const { txSig } = await this.txSender.send(
1399
- wrapInTx(await this.getSettleFundingPaymentIx(userAccount)),
2757
+ wrapInTx(await this.getSettleFundingPaymentIx(userAccountPublicKey)),
1400
2758
  [],
1401
2759
  this.opts
1402
2760
  );
@@ -1404,33 +2762,28 @@ export class ClearingHouse {
1404
2762
  }
1405
2763
 
1406
2764
  public async getSettleFundingPaymentIx(
1407
- userAccount: PublicKey
2765
+ userAccountPublicKey: PublicKey
1408
2766
  ): Promise<TransactionInstruction> {
1409
- const user = (await this.program.account.user.fetch(
1410
- userAccount
2767
+ const userAccount = (await this.program.account.user.fetch(
2768
+ userAccountPublicKey
1411
2769
  )) as UserAccount;
1412
2770
 
1413
- const userPositions = user.positions;
1414
-
1415
- const remainingAccounts = [];
1416
- for (const position of userPositions) {
2771
+ const writablePerpMarketIndexes = [];
2772
+ for (const position of userAccount.perpPositions) {
1417
2773
  if (!positionIsAvailable(position)) {
1418
- const marketPublicKey = await getMarketPublicKey(
1419
- this.program.programId,
1420
- position.marketIndex
1421
- );
1422
- remainingAccounts.push({
1423
- pubkey: marketPublicKey,
1424
- isWritable: false,
1425
- isSigner: false,
1426
- });
2774
+ writablePerpMarketIndexes.push(position.marketIndex);
1427
2775
  }
1428
2776
  }
1429
2777
 
2778
+ const remainingAccounts = this.getRemainingAccounts({
2779
+ userAccounts: [userAccount],
2780
+ writablePerpMarketIndexes,
2781
+ });
2782
+
1430
2783
  return await this.program.instruction.settleFundingPayment({
1431
2784
  accounts: {
1432
2785
  state: await this.getStatePublicKey(),
1433
- user: userAccount,
2786
+ user: userAccountPublicKey,
1434
2787
  },
1435
2788
  remainingAccounts,
1436
2789
  });
@@ -1440,10 +2793,256 @@ export class ClearingHouse {
1440
2793
  this.eventEmitter.emit(eventName, data);
1441
2794
  }
1442
2795
 
1443
- public getOracleDataForMarket(marketIndex: BN): OraclePriceData {
1444
- const oracleKey = this.getMarketAccount(marketIndex).amm.oracle;
2796
+ public getOracleDataForMarket(marketIndex: number): OraclePriceData {
2797
+ const oracleKey = this.getPerpMarketAccount(marketIndex).amm.oracle;
1445
2798
  const oracleData = this.getOraclePriceDataAndSlot(oracleKey).data;
1446
2799
 
1447
2800
  return oracleData;
1448
2801
  }
2802
+
2803
+ public getOracleDataForSpotMarket(marketIndex: number): OraclePriceData {
2804
+ const oracleKey = this.getSpotMarketAccount(marketIndex).oracle;
2805
+ const oracleData = this.getOraclePriceDataAndSlot(oracleKey).data;
2806
+
2807
+ return oracleData;
2808
+ }
2809
+
2810
+ public async initializeInsuranceFundStake(
2811
+ marketIndex: number
2812
+ ): Promise<TransactionSignature> {
2813
+ const { txSig } = await this.txSender.send(
2814
+ wrapInTx(await this.getInitializeInsuranceFundStakeIx(marketIndex)),
2815
+ [],
2816
+ this.opts
2817
+ );
2818
+ return txSig;
2819
+ }
2820
+
2821
+ public async getInitializeInsuranceFundStakeIx(
2822
+ marketIndex: number
2823
+ ): Promise<TransactionInstruction> {
2824
+ const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2825
+ this.program.programId,
2826
+ this.wallet.publicKey,
2827
+ marketIndex
2828
+ );
2829
+
2830
+ return await this.program.instruction.initializeInsuranceFundStake(
2831
+ marketIndex,
2832
+ {
2833
+ accounts: {
2834
+ insuranceFundStake: ifStakeAccountPublicKey,
2835
+ spotMarket: this.getSpotMarketAccount(marketIndex).pubkey,
2836
+ userStats: this.getUserStatsAccountPublicKey(),
2837
+ authority: this.wallet.publicKey,
2838
+ payer: this.wallet.publicKey,
2839
+ rent: anchor.web3.SYSVAR_RENT_PUBKEY,
2840
+ systemProgram: anchor.web3.SystemProgram.programId,
2841
+ state: await this.getStatePublicKey(),
2842
+ },
2843
+ }
2844
+ );
2845
+ }
2846
+
2847
+ public async addInsuranceFundStake(
2848
+ marketIndex: number,
2849
+ amount: BN,
2850
+ collateralAccountPublicKey: PublicKey
2851
+ ): Promise<TransactionSignature> {
2852
+ const spotMarket = this.getSpotMarketAccount(marketIndex);
2853
+ const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2854
+ this.program.programId,
2855
+ this.wallet.publicKey,
2856
+ marketIndex
2857
+ );
2858
+
2859
+ const remainingAccounts = this.getRemainingAccounts({
2860
+ userAccounts: [this.getUserAccount()],
2861
+ useMarketLastSlotCache: true,
2862
+ writableSpotMarketIndexes: [marketIndex],
2863
+ });
2864
+
2865
+ return await this.program.rpc.addInsuranceFundStake(marketIndex, amount, {
2866
+ accounts: {
2867
+ state: await this.getStatePublicKey(),
2868
+ spotMarket: spotMarket.pubkey,
2869
+ insuranceFundStake: ifStakeAccountPublicKey,
2870
+ userStats: this.getUserStatsAccountPublicKey(),
2871
+ authority: this.wallet.publicKey,
2872
+ spotMarketVault: spotMarket.vault,
2873
+ insuranceFundVault: spotMarket.insuranceFundVault,
2874
+ clearingHouseSigner: this.getSignerPublicKey(),
2875
+ userTokenAccount: collateralAccountPublicKey,
2876
+ tokenProgram: TOKEN_PROGRAM_ID,
2877
+ },
2878
+ remainingAccounts,
2879
+ });
2880
+ }
2881
+
2882
+ public async requestRemoveInsuranceFundStake(
2883
+ marketIndex: number,
2884
+ amount: BN
2885
+ ): Promise<TransactionSignature> {
2886
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
2887
+ const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2888
+ this.program.programId,
2889
+ this.wallet.publicKey,
2890
+ marketIndex
2891
+ );
2892
+
2893
+ const remainingAccounts = this.getRemainingAccounts({
2894
+ userAccounts: [this.getUserAccount()],
2895
+ useMarketLastSlotCache: true,
2896
+ writableSpotMarketIndexes: [marketIndex],
2897
+ });
2898
+
2899
+ return await this.program.rpc.requestRemoveInsuranceFundStake(
2900
+ marketIndex,
2901
+ amount,
2902
+ {
2903
+ accounts: {
2904
+ state: await this.getStatePublicKey(),
2905
+ spotMarket: spotMarketAccount.pubkey,
2906
+ insuranceFundStake: ifStakeAccountPublicKey,
2907
+ userStats: this.getUserStatsAccountPublicKey(),
2908
+ authority: this.wallet.publicKey,
2909
+ insuranceFundVault: spotMarketAccount.insuranceFundVault,
2910
+ },
2911
+ remainingAccounts,
2912
+ }
2913
+ );
2914
+ }
2915
+
2916
+ public async cancelRequestRemoveInsuranceFundStake(
2917
+ marketIndex: number
2918
+ ): Promise<TransactionSignature> {
2919
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
2920
+ const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2921
+ this.program.programId,
2922
+ this.wallet.publicKey,
2923
+ marketIndex
2924
+ );
2925
+
2926
+ const remainingAccounts = this.getRemainingAccounts({
2927
+ userAccounts: [this.getUserAccount()],
2928
+ useMarketLastSlotCache: true,
2929
+ writableSpotMarketIndexes: [marketIndex],
2930
+ });
2931
+
2932
+ return await this.program.rpc.cancelRequestRemoveInsuranceFundStake(
2933
+ marketIndex,
2934
+ {
2935
+ accounts: {
2936
+ state: await this.getStatePublicKey(),
2937
+ spotMarket: spotMarketAccount.pubkey,
2938
+ insuranceFundStake: ifStakeAccountPublicKey,
2939
+ userStats: this.getUserStatsAccountPublicKey(),
2940
+ authority: this.wallet.publicKey,
2941
+ insuranceFundVault: spotMarketAccount.insuranceFundVault,
2942
+ },
2943
+ remainingAccounts,
2944
+ }
2945
+ );
2946
+ }
2947
+
2948
+ public async removeInsuranceFundStake(
2949
+ marketIndex: number,
2950
+ collateralAccountPublicKey: PublicKey
2951
+ ): Promise<TransactionSignature> {
2952
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
2953
+ const ifStakeAccountPublicKey = getInsuranceFundStakeAccountPublicKey(
2954
+ this.program.programId,
2955
+ this.wallet.publicKey,
2956
+ marketIndex
2957
+ );
2958
+
2959
+ const remainingAccounts = this.getRemainingAccounts({
2960
+ userAccounts: [this.getUserAccount()],
2961
+ useMarketLastSlotCache: true,
2962
+ writableSpotMarketIndexes: [marketIndex],
2963
+ });
2964
+
2965
+ return await this.program.rpc.removeInsuranceFundStake(marketIndex, {
2966
+ accounts: {
2967
+ state: await this.getStatePublicKey(),
2968
+ spotMarket: spotMarketAccount.pubkey,
2969
+ insuranceFundStake: ifStakeAccountPublicKey,
2970
+ userStats: this.getUserStatsAccountPublicKey(),
2971
+ authority: this.wallet.publicKey,
2972
+ insuranceFundVault: spotMarketAccount.insuranceFundVault,
2973
+ clearingHouseSigner: this.getSignerPublicKey(),
2974
+ userTokenAccount: collateralAccountPublicKey,
2975
+ tokenProgram: TOKEN_PROGRAM_ID,
2976
+ },
2977
+ remainingAccounts,
2978
+ });
2979
+ }
2980
+
2981
+ public async settleRevenueToInsuranceFund(
2982
+ marketIndex: number
2983
+ ): Promise<TransactionSignature> {
2984
+ const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
2985
+
2986
+ const remainingAccounts = this.getRemainingAccounts({
2987
+ userAccounts: [this.getUserAccount()],
2988
+ useMarketLastSlotCache: true,
2989
+ writableSpotMarketIndexes: [marketIndex],
2990
+ });
2991
+
2992
+ return await this.program.rpc.settleRevenueToInsuranceFund(marketIndex, {
2993
+ accounts: {
2994
+ state: await this.getStatePublicKey(),
2995
+ spotMarket: spotMarketAccount.pubkey,
2996
+ spotMarketVault: spotMarketAccount.vault,
2997
+ clearingHouseSigner: this.getSignerPublicKey(),
2998
+ insuranceFundVault: spotMarketAccount.insuranceFundVault,
2999
+ tokenProgram: TOKEN_PROGRAM_ID,
3000
+ },
3001
+ remainingAccounts,
3002
+ });
3003
+ }
3004
+
3005
+ public async resolvePerpPnlDeficit(
3006
+ spotMarketIndex: number,
3007
+ perpMarketIndex: number
3008
+ ): Promise<TransactionSignature> {
3009
+ const { txSig } = await this.txSender.send(
3010
+ wrapInTx(
3011
+ await this.getResolvePerpPnlDeficitIx(spotMarketIndex, perpMarketIndex)
3012
+ ),
3013
+ [],
3014
+ this.opts
3015
+ );
3016
+ return txSig;
3017
+ }
3018
+
3019
+ public async getResolvePerpPnlDeficitIx(
3020
+ spotMarketIndex: number,
3021
+ perpMarketIndex: number
3022
+ ): Promise<TransactionInstruction> {
3023
+ const remainingAccounts = this.getRemainingAccounts({
3024
+ userAccounts: [this.getUserAccount()],
3025
+ useMarketLastSlotCache: true,
3026
+ writablePerpMarketIndexes: [perpMarketIndex],
3027
+ writableSpotMarketIndexes: [spotMarketIndex],
3028
+ });
3029
+
3030
+ const spotMarket = this.getSpotMarketAccount(spotMarketIndex);
3031
+
3032
+ return await this.program.instruction.resolvePerpPnlDeficit(
3033
+ spotMarketIndex,
3034
+ perpMarketIndex,
3035
+ {
3036
+ accounts: {
3037
+ state: await this.getStatePublicKey(),
3038
+ authority: this.wallet.publicKey,
3039
+ spotMarketVault: spotMarket.vault,
3040
+ insuranceFundVault: spotMarket.insuranceFundVault,
3041
+ clearingHouseSigner: this.getSignerPublicKey(),
3042
+ tokenProgram: TOKEN_PROGRAM_ID,
3043
+ },
3044
+ remainingAccounts: remainingAccounts,
3045
+ }
3046
+ );
3047
+ }
1449
3048
  }