@drift-labs/sdk 2.20.0 → 2.21.0-beta.1

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.
@@ -14,8 +14,9 @@ export declare class PollingUserAccountSubscriber implements UserAccountSubscrib
14
14
  accountLoader: BulkAccountLoader;
15
15
  accountsToPoll: Map<string, AccountToPoll>;
16
16
  errorCallbackId?: string;
17
+ lazyDecode: boolean;
17
18
  user?: DataAndSlot<UserAccount>;
18
- constructor(program: Program, userAccountPublicKey: PublicKey, accountLoader: BulkAccountLoader);
19
+ constructor(program: Program, userAccountPublicKey: PublicKey, accountLoader: BulkAccountLoader, lazyDecode?: boolean);
19
20
  subscribe(): Promise<boolean>;
20
21
  addToAccountLoader(): Promise<void>;
21
22
  fetchIfUnloaded(): Promise<void>;
@@ -5,13 +5,14 @@ const types_1 = require("./types");
5
5
  const events_1 = require("events");
6
6
  const utils_1 = require("./utils");
7
7
  class PollingUserAccountSubscriber {
8
- constructor(program, userAccountPublicKey, accountLoader) {
8
+ constructor(program, userAccountPublicKey, accountLoader, lazyDecode = false) {
9
9
  this.accountsToPoll = new Map();
10
10
  this.isSubscribed = false;
11
11
  this.program = program;
12
12
  this.accountLoader = accountLoader;
13
13
  this.eventEmitter = new events_1.EventEmitter();
14
14
  this.userAccountPublicKey = userAccountPublicKey;
15
+ this.lazyDecode = lazyDecode;
15
16
  }
16
17
  async subscribe() {
17
18
  if (this.isSubscribed) {
@@ -40,7 +41,9 @@ class PollingUserAccountSubscriber {
40
41
  return;
41
42
  }
42
43
  const account = this.program.account[accountToPoll.key].coder.accounts.decode((0, utils_1.capitalize)(accountToPoll.key), buffer);
43
- this[accountToPoll.key] = { data: account, slot };
44
+ if (!this.lazyDecode) {
45
+ this[accountToPoll.key] = { data: account, slot };
46
+ }
44
47
  // @ts-ignore
45
48
  this.eventEmitter.emit(accountToPoll.eventType, account);
46
49
  this.eventEmitter.emit('update');
@@ -53,7 +56,12 @@ class PollingUserAccountSubscriber {
53
56
  async fetchIfUnloaded() {
54
57
  let shouldFetch = false;
55
58
  for (const [_, accountToPoll] of this.accountsToPoll) {
56
- if (this[accountToPoll.key] === undefined) {
59
+ if (!this.lazyDecode && this[accountToPoll.key] === undefined) {
60
+ shouldFetch = true;
61
+ break;
62
+ }
63
+ else if (this.lazyDecode &&
64
+ this.accountLoader.bufferAndSlotMap.has(accountToPoll.publicKey.toString())) {
57
65
  shouldFetch = true;
58
66
  break;
59
67
  }
@@ -66,7 +74,7 @@ class PollingUserAccountSubscriber {
66
74
  await this.accountLoader.load();
67
75
  for (const [_, accountToPoll] of this.accountsToPoll) {
68
76
  const { buffer, slot } = this.accountLoader.getBufferAndSlot(accountToPoll.publicKey);
69
- if (buffer) {
77
+ if (buffer && !this.lazyDecode) {
70
78
  const account = this.program.account[accountToPoll.key].coder.accounts.decode((0, utils_1.capitalize)(accountToPoll.key), buffer);
71
79
  this[accountToPoll.key] = { data: account, slot };
72
80
  }
@@ -75,7 +83,7 @@ class PollingUserAccountSubscriber {
75
83
  doAccountsExist() {
76
84
  let success = true;
77
85
  for (const [_, accountToPoll] of this.accountsToPoll) {
78
- if (!this[accountToPoll.key]) {
86
+ if (!this.accountLoader.bufferAndSlotMap.has(accountToPoll.publicKey.toString())) {
79
87
  success = false;
80
88
  break;
81
89
  }
@@ -101,7 +109,14 @@ class PollingUserAccountSubscriber {
101
109
  }
102
110
  getUserAccountAndSlot() {
103
111
  this.assertIsSubscribed();
104
- return this.user;
112
+ if (!this.lazyDecode) {
113
+ return this.user;
114
+ }
115
+ else {
116
+ const { buffer, slot } = this.accountLoader.getBufferAndSlot(this.userAccountPublicKey);
117
+ const account = this.program.account.user.coder.accounts.decode('User', buffer);
118
+ return { data: account, slot };
119
+ }
105
120
  }
106
121
  }
107
122
  exports.PollingUserAccountSubscriber = PollingUserAccountSubscriber;
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="bn.js" />
3
- import { AnchorProvider, BN, Program } from '@project-serum/anchor';
3
+ import { AnchorProvider, BN, Program, ProgramAccount } from '@project-serum/anchor';
4
4
  import { StateAccount, IWallet, PositionDirection, UserAccount, PerpMarketAccount, OrderParams, Order, SpotMarketAccount, SpotPosition, MakerInfo, TakerInfo, OptionalOrderParams, OrderType, ReferrerInfo, MarketType, TxParams, SerumV3FulfillmentConfigAccount, ReferrerNameAccount, OrderTriggerCondition, PerpMarketExtendedInfo } from './types';
5
5
  import * as anchor from '@project-serum/anchor';
6
6
  import { Connection, PublicKey, TransactionSignature, ConfirmOptions, Transaction, TransactionInstruction, AccountMeta, Signer, AddressLookupTableAccount } from '@solana/web3.js';
@@ -101,6 +101,7 @@ export declare class DriftClient {
101
101
  updateUserCustomMarginRatio(marginRatio: number, subAccountId?: number): Promise<TransactionSignature>;
102
102
  updateUserMarginTradingEnabled(marginTradingEnabled: boolean, subAccountId?: number): Promise<TransactionSignature>;
103
103
  updateUserDelegate(delegate: PublicKey, subAccountId?: number): Promise<TransactionSignature>;
104
+ fetchAllUserAccounts(includeIdle?: boolean): Promise<ProgramAccount<UserAccount>[]>;
104
105
  getUserAccountsForDelegate(delegate: PublicKey): Promise<UserAccount[]>;
105
106
  getUserAccountsForAuthority(authority: PublicKey): Promise<UserAccount[]>;
106
107
  deleteUser(subAccountId?: number, txParams?: TxParams): Promise<TransactionSignature>;
@@ -190,6 +191,7 @@ export declare class DriftClient {
190
191
  getCancelOrdersIx(marketType: MarketType | null, marketIndex: number | null, direction: PositionDirection | null): Promise<TransactionInstruction>;
191
192
  fillPerpOrder(userAccountPublicKey: PublicKey, user: UserAccount, order?: Pick<Order, 'marketIndex' | 'orderId'>, makerInfo?: MakerInfo | MakerInfo[], referrerInfo?: ReferrerInfo, txParams?: TxParams): Promise<TransactionSignature>;
192
193
  getFillPerpOrderIx(userAccountPublicKey: PublicKey, userAccount: UserAccount, order: Pick<Order, 'marketIndex' | 'orderId'>, makerInfo?: MakerInfo | MakerInfo[], referrerInfo?: ReferrerInfo): Promise<TransactionInstruction>;
194
+ getRevertFillIx(): Promise<TransactionInstruction>;
193
195
  placeSpotOrder(orderParams: OptionalOrderParams, txParams?: TxParams): Promise<TransactionSignature>;
194
196
  getPlaceSpotOrderIx(orderParams: OptionalOrderParams): Promise<TransactionInstruction>;
195
197
  fillSpotOrder(userAccountPublicKey: PublicKey, user: UserAccount, order?: Order, fulfillmentConfig?: SerumV3FulfillmentConfigAccount, makerInfo?: MakerInfo, referrerInfo?: ReferrerInfo, txParams?: TxParams): Promise<TransactionSignature>;
@@ -200,6 +202,8 @@ export declare class DriftClient {
200
202
  getTriggerOrderIx(userAccountPublicKey: PublicKey, userAccount: UserAccount, order: Order): Promise<TransactionInstruction>;
201
203
  forceCancelOrders(userAccountPublicKey: PublicKey, user: UserAccount, txParams?: TxParams): Promise<TransactionSignature>;
202
204
  getForceCancelOrdersIx(userAccountPublicKey: PublicKey, userAccount: UserAccount): Promise<TransactionInstruction>;
205
+ updateUserIdle(userAccountPublicKey: PublicKey, user: UserAccount, txParams?: TxParams): Promise<TransactionSignature>;
206
+ getUpdateUserIdleIx(userAccountPublicKey: PublicKey, userAccount: UserAccount): Promise<TransactionInstruction>;
203
207
  placeAndTakePerpOrder(orderParams: OptionalOrderParams, makerInfo?: MakerInfo | MakerInfo[], referrerInfo?: ReferrerInfo, txParams?: TxParams): Promise<TransactionSignature>;
204
208
  getPlaceAndTakePerpOrderIx(orderParams: OptionalOrderParams, makerInfo?: MakerInfo | MakerInfo[], referrerInfo?: ReferrerInfo): Promise<TransactionInstruction>;
205
209
  placeAndMakePerpOrder(orderParams: OptionalOrderParams, takerInfo: TakerInfo, referrerInfo?: ReferrerInfo, txParams?: TxParams): Promise<TransactionSignature>;
@@ -79,6 +79,7 @@ class DriftClient {
79
79
  ? {
80
80
  type: 'polling',
81
81
  accountLoader: config.accountSubscription.accountLoader,
82
+ lazyDecode: config.accountSubscription.lazyDecode,
82
83
  }
83
84
  : {
84
85
  type: 'websocket',
@@ -424,6 +425,20 @@ class DriftClient {
424
425
  const { txSig } = await this.sendTransaction(tx, [], this.opts);
425
426
  return txSig;
426
427
  }
428
+ async fetchAllUserAccounts(includeIdle = true) {
429
+ let filters = undefined;
430
+ if (!includeIdle) {
431
+ filters = [
432
+ {
433
+ memcmp: {
434
+ offset: 4350,
435
+ bytes: bs58_1.default.encode(Uint8Array.from([0])),
436
+ },
437
+ },
438
+ ];
439
+ }
440
+ return (await this.program.account.user.all(filters));
441
+ }
427
442
  async getUserAccountsForDelegate(delegate) {
428
443
  const programAccounts = await this.program.account.user.all([
429
444
  {
@@ -1343,6 +1358,18 @@ class DriftClient {
1343
1358
  remainingAccounts,
1344
1359
  });
1345
1360
  }
1361
+ async getRevertFillIx() {
1362
+ const fillerPublicKey = await this.getUserAccountPublicKey();
1363
+ const fillerStatsPublicKey = this.getUserStatsAccountPublicKey();
1364
+ return this.program.instruction.revertFill({
1365
+ accounts: {
1366
+ state: await this.getStatePublicKey(),
1367
+ filler: fillerPublicKey,
1368
+ fillerStats: fillerStatsPublicKey,
1369
+ authority: this.wallet.publicKey,
1370
+ },
1371
+ });
1372
+ }
1346
1373
  async placeSpotOrder(orderParams, txParams) {
1347
1374
  const { txSig, slot } = await this.sendTransaction((0, utils_1.wrapInTx)(await this.getPlaceSpotOrderIx(orderParams), txParams === null || txParams === void 0 ? void 0 : txParams.computeUnits, txParams === null || txParams === void 0 ? void 0 : txParams.computeUnitsPrice), [], this.opts);
1348
1375
  this.spotMarketLastSlotCache.set(orderParams.marketIndex, slot);
@@ -1578,6 +1605,25 @@ class DriftClient {
1578
1605
  remainingAccounts,
1579
1606
  });
1580
1607
  }
1608
+ async updateUserIdle(userAccountPublicKey, user, txParams) {
1609
+ const { txSig } = await this.txSender.send((0, utils_1.wrapInTx)(await this.getUpdateUserIdleIx(userAccountPublicKey, user), txParams === null || txParams === void 0 ? void 0 : txParams.computeUnits, txParams === null || txParams === void 0 ? void 0 : txParams.computeUnitsPrice), [], this.opts);
1610
+ return txSig;
1611
+ }
1612
+ async getUpdateUserIdleIx(userAccountPublicKey, userAccount) {
1613
+ const fillerPublicKey = await this.getUserAccountPublicKey();
1614
+ const remainingAccounts = this.getRemainingAccounts({
1615
+ userAccounts: [userAccount],
1616
+ });
1617
+ return await this.program.instruction.updateUserIdle({
1618
+ accounts: {
1619
+ state: await this.getStatePublicKey(),
1620
+ filler: fillerPublicKey,
1621
+ user: userAccountPublicKey,
1622
+ authority: this.wallet.publicKey,
1623
+ },
1624
+ remainingAccounts,
1625
+ });
1626
+ }
1581
1627
  async placeAndTakePerpOrder(orderParams, makerInfo, referrerInfo, txParams) {
1582
1628
  const { txSig, slot } = await this.sendTransaction((0, utils_1.wrapInTx)(await this.getPlaceAndTakePerpOrderIx(orderParams, makerInfo, referrerInfo), txParams === null || txParams === void 0 ? void 0 : txParams.computeUnits, txParams === null || txParams === void 0 ? void 0 : txParams.computeUnitsPrice), [], this.opts);
1583
1629
  this.perpMarketLastSlotCache.set(orderParams.marketIndex, slot);
@@ -25,6 +25,7 @@ export type DriftClientSubscriptionConfig = {
25
25
  } | {
26
26
  type: 'polling';
27
27
  accountLoader: BulkAccountLoader;
28
+ lazyDecode?: boolean;
28
29
  };
29
30
  type TxSenderConfig = {
30
31
  type: 'retry';
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.20.0",
2
+ "version": "2.21.0-beta.1",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -909,6 +909,32 @@
909
909
  }
910
910
  ]
911
911
  },
912
+ {
913
+ "name": "revertFill",
914
+ "accounts": [
915
+ {
916
+ "name": "state",
917
+ "isMut": false,
918
+ "isSigner": false
919
+ },
920
+ {
921
+ "name": "authority",
922
+ "isMut": false,
923
+ "isSigner": true
924
+ },
925
+ {
926
+ "name": "filler",
927
+ "isMut": true,
928
+ "isSigner": false
929
+ },
930
+ {
931
+ "name": "fillerStats",
932
+ "isMut": true,
933
+ "isSigner": false
934
+ }
935
+ ],
936
+ "args": []
937
+ },
912
938
  {
913
939
  "name": "fillSpotOrder",
914
940
  "accounts": [
@@ -1023,6 +1049,32 @@
1023
1049
  ],
1024
1050
  "args": []
1025
1051
  },
1052
+ {
1053
+ "name": "updateUserIdle",
1054
+ "accounts": [
1055
+ {
1056
+ "name": "state",
1057
+ "isMut": false,
1058
+ "isSigner": false
1059
+ },
1060
+ {
1061
+ "name": "authority",
1062
+ "isMut": false,
1063
+ "isSigner": true
1064
+ },
1065
+ {
1066
+ "name": "filler",
1067
+ "isMut": true,
1068
+ "isSigner": false
1069
+ },
1070
+ {
1071
+ "name": "user",
1072
+ "isMut": true,
1073
+ "isSigner": false
1074
+ }
1075
+ ],
1076
+ "args": []
1077
+ },
1026
1078
  {
1027
1079
  "name": "settlePnl",
1028
1080
  "accounts": [
@@ -4543,7 +4595,7 @@
4543
4595
  "type": "u64"
4544
4596
  },
4545
4597
  {
4546
- "name": "liquidationStartSlot",
4598
+ "name": "lastActiveSlot",
4547
4599
  "type": "u64"
4548
4600
  },
4549
4601
  {
@@ -4572,12 +4624,16 @@
4572
4624
  "name": "isMarginTradingEnabled",
4573
4625
  "type": "bool"
4574
4626
  },
4627
+ {
4628
+ "name": "idle",
4629
+ "type": "bool"
4630
+ },
4575
4631
  {
4576
4632
  "name": "padding",
4577
4633
  "type": {
4578
4634
  "array": [
4579
4635
  "u8",
4580
- 26
4636
+ 25
4581
4637
  ]
4582
4638
  }
4583
4639
  }
@@ -8733,6 +8789,16 @@
8733
8789
  "code": 6237,
8734
8790
  "name": "UnableToLoadUserStatsAccount",
8735
8791
  "msg": "UnableToLoadUserStatsAccount"
8792
+ },
8793
+ {
8794
+ "code": 6238,
8795
+ "name": "UserNotInactive",
8796
+ "msg": "User Not Inactive"
8797
+ },
8798
+ {
8799
+ "code": 6239,
8800
+ "name": "RevertFill",
8801
+ "msg": "RevertFill"
8736
8802
  }
8737
8803
  ]
8738
8804
  }
@@ -31,12 +31,12 @@ class RetryTxSender {
31
31
  async prepareTx(tx, additionalSigners, opts) {
32
32
  tx.feePayer = this.provider.wallet.publicKey;
33
33
  tx.recentBlockhash = (await this.provider.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash;
34
- const signedTx = await this.provider.wallet.signTransaction(tx);
35
34
  additionalSigners
36
35
  .filter((s) => s !== undefined)
37
36
  .forEach((kp) => {
38
- signedTx.partialSign(kp);
37
+ tx.partialSign(kp);
39
38
  });
39
+ const signedTx = await this.provider.wallet.signTransaction(tx);
40
40
  return signedTx;
41
41
  }
42
42
  async sendVersionedTransaction(ixs, lookupTableAccounts, additionalSigners, opts) {
package/lib/types.d.ts CHANGED
@@ -845,8 +845,9 @@ export type UserAccount = {
845
845
  totalSocialLoss: BN;
846
846
  cumulativePerpFunding: BN;
847
847
  liquidationMarginFreed: BN;
848
- liquidationStartSlot: BN;
848
+ lastActiveSlot: BN;
849
849
  isMarginTradingEnabled: boolean;
850
+ idle: boolean;
850
851
  };
851
852
  export type SpotPosition = {
852
853
  marketIndex: number;
package/lib/user.d.ts CHANGED
@@ -269,6 +269,7 @@ export declare class User {
269
269
  depositAmount: BN;
270
270
  maxDepositAmount: BN;
271
271
  };
272
+ canMakeIdle(slot: BN, slotsBeforeIdle: BN): boolean;
272
273
  /**
273
274
  * Get the total position value, excluding any position coming from the given target market
274
275
  * @param marketToIgnore
package/lib/user.js CHANGED
@@ -25,7 +25,7 @@ class User {
25
25
  this.driftClient = config.driftClient;
26
26
  this.userAccountPublicKey = config.userAccountPublicKey;
27
27
  if (((_a = config.accountSubscription) === null || _a === void 0 ? void 0 : _a.type) === 'polling') {
28
- this.accountSubscriber = new pollingUserAccountSubscriber_1.PollingUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.accountLoader);
28
+ this.accountSubscriber = new pollingUserAccountSubscriber_1.PollingUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey, config.accountSubscription.accountLoader, config.accountSubscription.lazyDecode);
29
29
  }
30
30
  else {
31
31
  this.accountSubscriber = new webSocketUserAccountSubscriber_1.WebSocketUserAccountSubscriber(config.driftClient.program, config.userAccountPublicKey);
@@ -1212,6 +1212,38 @@ class User {
1212
1212
  depositAmount,
1213
1213
  };
1214
1214
  }
1215
+ canMakeIdle(slot, slotsBeforeIdle) {
1216
+ const userAccount = this.getUserAccount();
1217
+ if (userAccount.idle) {
1218
+ return false;
1219
+ }
1220
+ const userLastActiveSlot = userAccount.lastActiveSlot;
1221
+ if (userLastActiveSlot.lt(slotsBeforeIdle)) {
1222
+ return false;
1223
+ }
1224
+ if (this.isBeingLiquidated()) {
1225
+ return false;
1226
+ }
1227
+ for (const perpPosition of userAccount.perpPositions) {
1228
+ if (!(0, position_1.positionIsAvailable)(perpPosition)) {
1229
+ return false;
1230
+ }
1231
+ }
1232
+ for (const spotPosition of userAccount.spotPositions) {
1233
+ if ((0, types_1.isVariant)(spotPosition.balanceType, 'borrow')) {
1234
+ return false;
1235
+ }
1236
+ if (spotPosition.openOrders !== 0) {
1237
+ return false;
1238
+ }
1239
+ }
1240
+ for (const order of userAccount.orders) {
1241
+ if (!(0, types_1.isVariant)(order.status, 'init')) {
1242
+ return false;
1243
+ }
1244
+ }
1245
+ return true;
1246
+ }
1215
1247
  /**
1216
1248
  * Get the total position value, excluding any position coming from the given target market
1217
1249
  * @param marketToIgnore
@@ -11,4 +11,5 @@ export type UserSubscriptionConfig = {
11
11
  } | {
12
12
  type: 'polling';
13
13
  accountLoader: BulkAccountLoader;
14
+ lazyDecode?: boolean;
14
15
  };
@@ -15,7 +15,7 @@ export declare class UserMap implements UserMapInterface {
15
15
  private driftClient;
16
16
  private accountSubscription;
17
17
  constructor(driftClient: DriftClient, accountSubscription: UserSubscriptionConfig);
18
- fetchAllUsers(): Promise<void>;
18
+ fetchAllUsers(includeIdle?: boolean): Promise<void>;
19
19
  addPubkey(userAccountPublicKey: PublicKey): Promise<void>;
20
20
  has(key: string): boolean;
21
21
  /**
@@ -9,10 +9,10 @@ class UserMap {
9
9
  this.driftClient = driftClient;
10
10
  this.accountSubscription = accountSubscription;
11
11
  }
12
- async fetchAllUsers() {
12
+ async fetchAllUsers(includeIdle = true) {
13
13
  const userArray = [];
14
14
  const userAccountArray = [];
15
- const programUserAccounts = (await this.driftClient.program.account.user.all());
15
+ const programUserAccounts = await this.driftClient.fetchAllUserAccounts(includeIdle);
16
16
  for (const programUserAccount of programUserAccounts) {
17
17
  if (this.userMap.has(programUserAccount.publicKey.toString())) {
18
18
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.20.0",
3
+ "version": "2.21.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -23,18 +23,22 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
23
23
  accountsToPoll = new Map<string, AccountToPoll>();
24
24
  errorCallbackId?: string;
25
25
 
26
+ lazyDecode: boolean;
27
+
26
28
  user?: DataAndSlot<UserAccount>;
27
29
 
28
30
  public constructor(
29
31
  program: Program,
30
32
  userAccountPublicKey: PublicKey,
31
- accountLoader: BulkAccountLoader
33
+ accountLoader: BulkAccountLoader,
34
+ lazyDecode = false
32
35
  ) {
33
36
  this.isSubscribed = false;
34
37
  this.program = program;
35
38
  this.accountLoader = accountLoader;
36
39
  this.eventEmitter = new EventEmitter();
37
40
  this.userAccountPublicKey = userAccountPublicKey;
41
+ this.lazyDecode = lazyDecode;
38
42
  }
39
43
 
40
44
  async subscribe(): Promise<boolean> {
@@ -75,7 +79,9 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
75
79
  const account = this.program.account[
76
80
  accountToPoll.key
77
81
  ].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
78
- this[accountToPoll.key] = { data: account, slot };
82
+ if (!this.lazyDecode) {
83
+ this[accountToPoll.key] = { data: account, slot };
84
+ }
79
85
  // @ts-ignore
80
86
  this.eventEmitter.emit(accountToPoll.eventType, account);
81
87
  this.eventEmitter.emit('update');
@@ -91,7 +97,15 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
91
97
  async fetchIfUnloaded(): Promise<void> {
92
98
  let shouldFetch = false;
93
99
  for (const [_, accountToPoll] of this.accountsToPoll) {
94
- if (this[accountToPoll.key] === undefined) {
100
+ if (!this.lazyDecode && this[accountToPoll.key] === undefined) {
101
+ shouldFetch = true;
102
+ break;
103
+ } else if (
104
+ this.lazyDecode &&
105
+ this.accountLoader.bufferAndSlotMap.has(
106
+ accountToPoll.publicKey.toString()
107
+ )
108
+ ) {
95
109
  shouldFetch = true;
96
110
  break;
97
111
  }
@@ -108,7 +122,7 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
108
122
  const { buffer, slot } = this.accountLoader.getBufferAndSlot(
109
123
  accountToPoll.publicKey
110
124
  );
111
- if (buffer) {
125
+ if (buffer && !this.lazyDecode) {
112
126
  const account = this.program.account[
113
127
  accountToPoll.key
114
128
  ].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
@@ -120,7 +134,11 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
120
134
  doAccountsExist(): boolean {
121
135
  let success = true;
122
136
  for (const [_, accountToPoll] of this.accountsToPoll) {
123
- if (!this[accountToPoll.key]) {
137
+ if (
138
+ !this.accountLoader.bufferAndSlotMap.has(
139
+ accountToPoll.publicKey.toString()
140
+ )
141
+ ) {
124
142
  success = false;
125
143
  break;
126
144
  }
@@ -158,6 +176,17 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
158
176
 
159
177
  public getUserAccountAndSlot(): DataAndSlot<UserAccount> {
160
178
  this.assertIsSubscribed();
161
- return this.user;
179
+ if (!this.lazyDecode) {
180
+ return this.user;
181
+ } else {
182
+ const { buffer, slot } = this.accountLoader.getBufferAndSlot(
183
+ this.userAccountPublicKey
184
+ );
185
+ const account = this.program.account.user.coder.accounts.decode(
186
+ 'User',
187
+ buffer
188
+ );
189
+ return { data: account, slot };
190
+ }
162
191
  }
163
192
  }
@@ -1,4 +1,10 @@
1
- import { AnchorProvider, BN, Idl, Program } from '@project-serum/anchor';
1
+ import {
2
+ AnchorProvider,
3
+ BN,
4
+ Idl,
5
+ Program,
6
+ ProgramAccount,
7
+ } from '@project-serum/anchor';
2
8
  import bs58 from 'bs58';
3
9
  import {
4
10
  ASSOCIATED_TOKEN_PROGRAM_ID,
@@ -154,6 +160,7 @@ export class DriftClient {
154
160
  ? {
155
161
  type: 'polling',
156
162
  accountLoader: config.accountSubscription.accountLoader,
163
+ lazyDecode: config.accountSubscription.lazyDecode,
157
164
  }
158
165
  : {
159
166
  type: 'websocket',
@@ -713,6 +720,25 @@ export class DriftClient {
713
720
  return txSig;
714
721
  }
715
722
 
723
+ public async fetchAllUserAccounts(
724
+ includeIdle = true
725
+ ): Promise<ProgramAccount<UserAccount>[]> {
726
+ let filters = undefined;
727
+ if (!includeIdle) {
728
+ filters = [
729
+ {
730
+ memcmp: {
731
+ offset: 4350,
732
+ bytes: bs58.encode(Uint8Array.from([0])),
733
+ },
734
+ },
735
+ ];
736
+ }
737
+ return (await this.program.account.user.all(
738
+ filters
739
+ )) as ProgramAccount<UserAccount>[];
740
+ }
741
+
716
742
  public async getUserAccountsForDelegate(
717
743
  delegate: PublicKey
718
744
  ): Promise<UserAccount[]> {
@@ -2369,6 +2395,20 @@ export class DriftClient {
2369
2395
  });
2370
2396
  }
2371
2397
 
2398
+ public async getRevertFillIx(): Promise<TransactionInstruction> {
2399
+ const fillerPublicKey = await this.getUserAccountPublicKey();
2400
+ const fillerStatsPublicKey = this.getUserStatsAccountPublicKey();
2401
+
2402
+ return this.program.instruction.revertFill({
2403
+ accounts: {
2404
+ state: await this.getStatePublicKey(),
2405
+ filler: fillerPublicKey,
2406
+ fillerStats: fillerStatsPublicKey,
2407
+ authority: this.wallet.publicKey,
2408
+ },
2409
+ });
2410
+ }
2411
+
2372
2412
  public async placeSpotOrder(
2373
2413
  orderParams: OptionalOrderParams,
2374
2414
  txParams?: TxParams
@@ -2734,6 +2774,44 @@ export class DriftClient {
2734
2774
  });
2735
2775
  }
2736
2776
 
2777
+ public async updateUserIdle(
2778
+ userAccountPublicKey: PublicKey,
2779
+ user: UserAccount,
2780
+ txParams?: TxParams
2781
+ ): Promise<TransactionSignature> {
2782
+ const { txSig } = await this.txSender.send(
2783
+ wrapInTx(
2784
+ await this.getUpdateUserIdleIx(userAccountPublicKey, user),
2785
+ txParams?.computeUnits,
2786
+ txParams?.computeUnitsPrice
2787
+ ),
2788
+ [],
2789
+ this.opts
2790
+ );
2791
+ return txSig;
2792
+ }
2793
+
2794
+ public async getUpdateUserIdleIx(
2795
+ userAccountPublicKey: PublicKey,
2796
+ userAccount: UserAccount
2797
+ ): Promise<TransactionInstruction> {
2798
+ const fillerPublicKey = await this.getUserAccountPublicKey();
2799
+
2800
+ const remainingAccounts = this.getRemainingAccounts({
2801
+ userAccounts: [userAccount],
2802
+ });
2803
+
2804
+ return await this.program.instruction.updateUserIdle({
2805
+ accounts: {
2806
+ state: await this.getStatePublicKey(),
2807
+ filler: fillerPublicKey,
2808
+ user: userAccountPublicKey,
2809
+ authority: this.wallet.publicKey,
2810
+ },
2811
+ remainingAccounts,
2812
+ });
2813
+ }
2814
+
2737
2815
  public async placeAndTakePerpOrder(
2738
2816
  orderParams: OptionalOrderParams,
2739
2817
  makerInfo?: MakerInfo | MakerInfo[],
@@ -29,6 +29,7 @@ export type DriftClientSubscriptionConfig =
29
29
  | {
30
30
  type: 'polling';
31
31
  accountLoader: BulkAccountLoader;
32
+ lazyDecode?: boolean;
32
33
  };
33
34
 
34
35
  type TxSenderConfig = {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.20.0",
2
+ "version": "2.21.0-beta.1",
3
3
  "name": "drift",
4
4
  "instructions": [
5
5
  {
@@ -909,6 +909,32 @@
909
909
  }
910
910
  ]
911
911
  },
912
+ {
913
+ "name": "revertFill",
914
+ "accounts": [
915
+ {
916
+ "name": "state",
917
+ "isMut": false,
918
+ "isSigner": false
919
+ },
920
+ {
921
+ "name": "authority",
922
+ "isMut": false,
923
+ "isSigner": true
924
+ },
925
+ {
926
+ "name": "filler",
927
+ "isMut": true,
928
+ "isSigner": false
929
+ },
930
+ {
931
+ "name": "fillerStats",
932
+ "isMut": true,
933
+ "isSigner": false
934
+ }
935
+ ],
936
+ "args": []
937
+ },
912
938
  {
913
939
  "name": "fillSpotOrder",
914
940
  "accounts": [
@@ -1023,6 +1049,32 @@
1023
1049
  ],
1024
1050
  "args": []
1025
1051
  },
1052
+ {
1053
+ "name": "updateUserIdle",
1054
+ "accounts": [
1055
+ {
1056
+ "name": "state",
1057
+ "isMut": false,
1058
+ "isSigner": false
1059
+ },
1060
+ {
1061
+ "name": "authority",
1062
+ "isMut": false,
1063
+ "isSigner": true
1064
+ },
1065
+ {
1066
+ "name": "filler",
1067
+ "isMut": true,
1068
+ "isSigner": false
1069
+ },
1070
+ {
1071
+ "name": "user",
1072
+ "isMut": true,
1073
+ "isSigner": false
1074
+ }
1075
+ ],
1076
+ "args": []
1077
+ },
1026
1078
  {
1027
1079
  "name": "settlePnl",
1028
1080
  "accounts": [
@@ -4543,7 +4595,7 @@
4543
4595
  "type": "u64"
4544
4596
  },
4545
4597
  {
4546
- "name": "liquidationStartSlot",
4598
+ "name": "lastActiveSlot",
4547
4599
  "type": "u64"
4548
4600
  },
4549
4601
  {
@@ -4572,12 +4624,16 @@
4572
4624
  "name": "isMarginTradingEnabled",
4573
4625
  "type": "bool"
4574
4626
  },
4627
+ {
4628
+ "name": "idle",
4629
+ "type": "bool"
4630
+ },
4575
4631
  {
4576
4632
  "name": "padding",
4577
4633
  "type": {
4578
4634
  "array": [
4579
4635
  "u8",
4580
- 26
4636
+ 25
4581
4637
  ]
4582
4638
  }
4583
4639
  }
@@ -8733,6 +8789,16 @@
8733
8789
  "code": 6237,
8734
8790
  "name": "UnableToLoadUserStatsAccount",
8735
8791
  "msg": "UnableToLoadUserStatsAccount"
8792
+ },
8793
+ {
8794
+ "code": 6238,
8795
+ "name": "UserNotInactive",
8796
+ "msg": "User Not Inactive"
8797
+ },
8798
+ {
8799
+ "code": 6239,
8800
+ "name": "RevertFill",
8801
+ "msg": "RevertFill"
8736
8802
  }
8737
8803
  ]
8738
8804
  }
@@ -75,13 +75,14 @@ export class RetryTxSender implements TxSender {
75
75
  )
76
76
  ).blockhash;
77
77
 
78
- const signedTx = await this.provider.wallet.signTransaction(tx);
79
78
  additionalSigners
80
79
  .filter((s): s is Signer => s !== undefined)
81
80
  .forEach((kp) => {
82
- signedTx.partialSign(kp);
81
+ tx.partialSign(kp);
83
82
  });
84
83
 
84
+ const signedTx = await this.provider.wallet.signTransaction(tx);
85
+
85
86
  return signedTx;
86
87
  }
87
88
 
package/src/types.ts CHANGED
@@ -798,8 +798,9 @@ export type UserAccount = {
798
798
  totalSocialLoss: BN;
799
799
  cumulativePerpFunding: BN;
800
800
  liquidationMarginFreed: BN;
801
- liquidationStartSlot: BN;
801
+ lastActiveSlot: BN;
802
802
  isMarginTradingEnabled: boolean;
803
+ idle: boolean;
803
804
  };
804
805
 
805
806
  export type SpotPosition = {
package/src/user.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  isOneOfVariant,
13
13
  PerpMarketAccount,
14
14
  } from './types';
15
- import { calculateEntryPrice } from './math/position';
15
+ import { calculateEntryPrice, positionIsAvailable } from './math/position';
16
16
  import {
17
17
  PRICE_PRECISION,
18
18
  AMM_TO_QUOTE_PRECISION_RATIO,
@@ -95,7 +95,8 @@ export class User {
95
95
  this.accountSubscriber = new PollingUserAccountSubscriber(
96
96
  config.driftClient.program,
97
97
  config.userAccountPublicKey,
98
- config.accountSubscription.accountLoader
98
+ config.accountSubscription.accountLoader,
99
+ config.accountSubscription.lazyDecode
99
100
  );
100
101
  } else {
101
102
  this.accountSubscriber = new WebSocketUserAccountSubscriber(
@@ -2171,6 +2172,46 @@ export class User {
2171
2172
  };
2172
2173
  }
2173
2174
 
2175
+ public canMakeIdle(slot: BN, slotsBeforeIdle: BN): boolean {
2176
+ const userAccount = this.getUserAccount();
2177
+ if (userAccount.idle) {
2178
+ return false;
2179
+ }
2180
+
2181
+ const userLastActiveSlot = userAccount.lastActiveSlot;
2182
+ if (userLastActiveSlot.lt(slotsBeforeIdle)) {
2183
+ return false;
2184
+ }
2185
+
2186
+ if (this.isBeingLiquidated()) {
2187
+ return false;
2188
+ }
2189
+
2190
+ for (const perpPosition of userAccount.perpPositions) {
2191
+ if (!positionIsAvailable(perpPosition)) {
2192
+ return false;
2193
+ }
2194
+ }
2195
+
2196
+ for (const spotPosition of userAccount.spotPositions) {
2197
+ if (isVariant(spotPosition.balanceType, 'borrow')) {
2198
+ return false;
2199
+ }
2200
+
2201
+ if (spotPosition.openOrders !== 0) {
2202
+ return false;
2203
+ }
2204
+ }
2205
+
2206
+ for (const order of userAccount.orders) {
2207
+ if (!isVariant(order.status, 'init')) {
2208
+ return false;
2209
+ }
2210
+ }
2211
+
2212
+ return true;
2213
+ }
2214
+
2174
2215
  /**
2175
2216
  * Get the total position value, excluding any position coming from the given target market
2176
2217
  * @param marketToIgnore
package/src/userConfig.ts CHANGED
@@ -15,4 +15,5 @@ export type UserSubscriptionConfig =
15
15
  | {
16
16
  type: 'polling';
17
17
  accountLoader: BulkAccountLoader;
18
+ lazyDecode?: boolean;
18
19
  };
@@ -14,7 +14,6 @@ import {
14
14
  NewUserRecord,
15
15
  LPRecord,
16
16
  } from '..';
17
- import { ProgramAccount } from '@project-serum/anchor';
18
17
 
19
18
  import { PublicKey } from '@solana/web3.js';
20
19
 
@@ -42,12 +41,13 @@ export class UserMap implements UserMapInterface {
42
41
  this.accountSubscription = accountSubscription;
43
42
  }
44
43
 
45
- public async fetchAllUsers() {
44
+ public async fetchAllUsers(includeIdle = true) {
46
45
  const userArray: User[] = [];
47
46
  const userAccountArray: UserAccount[] = [];
48
47
 
49
- const programUserAccounts =
50
- (await this.driftClient.program.account.user.all()) as ProgramAccount<UserAccount>[];
48
+ const programUserAccounts = await this.driftClient.fetchAllUserAccounts(
49
+ includeIdle
50
+ );
51
51
  for (const programUserAccount of programUserAccounts) {
52
52
  if (this.userMap.has(programUserAccount.publicKey.toString())) {
53
53
  continue;