@drift-labs/sdk 2.98.0-beta.11 → 2.98.0-beta.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.98.0-beta.11
1
+ 2.98.0-beta.13
@@ -3880,6 +3880,9 @@ class DriftClient {
3880
3880
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
3881
3881
  const isSolMarket = spotMarketAccount.mint.equals(spotMarkets_1.WRAPPED_SOL_MINT);
3882
3882
  const createWSOLTokenAccount = isSolMarket && collateralAccountPublicKey.equals(this.wallet.publicKey);
3883
+ // create associated token account because it may not exist
3884
+ const associatedTokenAccountPublicKey = (0, spl_token_1.getAssociatedTokenAddressSync)(spotMarketAccount.mint, this.wallet.publicKey, true);
3885
+ addIfStakeIxs.push(await (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(this.wallet.publicKey, associatedTokenAccountPublicKey, this.wallet.publicKey, spotMarketAccount.mint));
3883
3886
  let tokenAccount;
3884
3887
  if (!(await this.checkIfAccountExists(this.getUserStatsAccountPublicKey()))) {
3885
3888
  addIfStakeIxs.push(await this.getInitializeUserStatsIx());
@@ -106,6 +106,7 @@ export * from './dlob/DLOBApiClient';
106
106
  export * from './dlob/types';
107
107
  export * from './dlob/orderBookLevels';
108
108
  export * from './userMap/userMap';
109
+ export * from './userMap/referrerMap';
109
110
  export * from './userMap/userStatsMap';
110
111
  export * from './userMap/userMapConfig';
111
112
  export * from './math/bankruptcy';
@@ -129,6 +129,7 @@ __exportStar(require("./dlob/DLOBApiClient"), exports);
129
129
  __exportStar(require("./dlob/types"), exports);
130
130
  __exportStar(require("./dlob/orderBookLevels"), exports);
131
131
  __exportStar(require("./userMap/userMap"), exports);
132
+ __exportStar(require("./userMap/referrerMap"), exports);
132
133
  __exportStar(require("./userMap/userStatsMap"), exports);
133
134
  __exportStar(require("./userMap/userMapConfig"), exports);
134
135
  __exportStar(require("./math/bankruptcy"), exports);
@@ -5,3 +5,6 @@ export declare function getUserWithOrderFilter(): MemcmpFilter;
5
5
  export declare function getUserWithAuctionFilter(): MemcmpFilter;
6
6
  export declare function getUserThatHasBeenLP(): MemcmpFilter;
7
7
  export declare function getUserWithName(name: string): MemcmpFilter;
8
+ export declare function getUserStatsFilter(): MemcmpFilter;
9
+ export declare function getUserStatsIsReferredFilter(): MemcmpFilter;
10
+ export declare function getUserStatsIsReferredOrReferrerFilter(): MemcmpFilter;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getUserWithName = exports.getUserThatHasBeenLP = exports.getUserWithAuctionFilter = exports.getUserWithOrderFilter = exports.getNonIdleUserFilter = exports.getUserFilter = void 0;
6
+ exports.getUserStatsIsReferredOrReferrerFilter = exports.getUserStatsIsReferredFilter = exports.getUserStatsFilter = exports.getUserWithName = exports.getUserThatHasBeenLP = exports.getUserWithAuctionFilter = exports.getUserWithOrderFilter = exports.getNonIdleUserFilter = exports.getUserFilter = void 0;
7
7
  const bs58_1 = __importDefault(require("bs58"));
8
8
  const anchor_1 = require("@coral-xyz/anchor");
9
9
  const userName_1 = require("./userName");
@@ -61,3 +61,30 @@ function getUserWithName(name) {
61
61
  };
62
62
  }
63
63
  exports.getUserWithName = getUserWithName;
64
+ function getUserStatsFilter() {
65
+ return {
66
+ memcmp: {
67
+ offset: 0,
68
+ bytes: bs58_1.default.encode(anchor_1.BorshAccountsCoder.accountDiscriminator('UserStats')),
69
+ },
70
+ };
71
+ }
72
+ exports.getUserStatsFilter = getUserStatsFilter;
73
+ function getUserStatsIsReferredFilter() {
74
+ return {
75
+ memcmp: {
76
+ offset: 188,
77
+ bytes: bs58_1.default.encode(Buffer.from(Uint8Array.from([2]))),
78
+ },
79
+ };
80
+ }
81
+ exports.getUserStatsIsReferredFilter = getUserStatsIsReferredFilter;
82
+ function getUserStatsIsReferredOrReferrerFilter() {
83
+ return {
84
+ memcmp: {
85
+ offset: 188,
86
+ bytes: bs58_1.default.encode(Buffer.from(Uint8Array.from([3]))),
87
+ },
88
+ };
89
+ }
90
+ exports.getUserStatsIsReferredOrReferrerFilter = getUserStatsIsReferredOrReferrerFilter;
@@ -0,0 +1,45 @@
1
+ import { MemcmpFilter } from '@solana/web3.js';
2
+ import { BulkAccountLoader } from '../accounts/bulkAccountLoader';
3
+ import { DriftClient } from '../driftClient';
4
+ import { ReferrerInfo } from '../types';
5
+ export declare class ReferrerMap {
6
+ /**
7
+ * map from authority pubkey to ReferrerInfo.
8
+ * - if a user has not been entered into the map, the value is undefined
9
+ * - if a user has no referrer, the value is null
10
+ * - if a user has a referrer, the value is a ReferrerInfo object
11
+ */
12
+ private referrerMap;
13
+ private driftClient;
14
+ private bulkAccountLoader;
15
+ private parallelSync;
16
+ private fetchPromise?;
17
+ private fetchPromiseResolver;
18
+ /**
19
+ * Creates a new UserStatsMap instance.
20
+ *
21
+ * @param {DriftClient} driftClient - The DriftClient instance.
22
+ * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
23
+ */
24
+ constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader, parallelSync?: boolean);
25
+ /**
26
+ * Subscribe to all UserStats accounts.
27
+ */
28
+ subscribe(): Promise<void>;
29
+ has(authorityPublicKey: string): boolean;
30
+ get(authorityPublicKey: string): ReferrerInfo | undefined | null;
31
+ addReferrerInfo(authority: string, referrerInfo?: ReferrerInfo | null): Promise<void>;
32
+ /**
33
+ * Enforce that a UserStats will exist for the given authorityPublicKey,
34
+ * reading one from the blockchain if necessary.
35
+ * @param authorityPublicKey
36
+ * @returns
37
+ */
38
+ mustGet(authorityPublicKey: string): Promise<ReferrerInfo | null | undefined>;
39
+ values(): IterableIterator<ReferrerInfo | null>;
40
+ size(): number;
41
+ sync(): Promise<void>;
42
+ syncAll(): Promise<void>;
43
+ syncReferrer(referrerFilter: MemcmpFilter): Promise<void>;
44
+ unsubscribe(): Promise<void>;
45
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReferrerMap = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const bulkAccountLoader_1 = require("../accounts/bulkAccountLoader");
6
+ const userStats_1 = require("../userStats");
7
+ const pda_1 = require("../addresses/pda");
8
+ const memcmp_1 = require("../memcmp");
9
+ const bytes_1 = require("@coral-xyz/anchor/dist/cjs/utils/bytes");
10
+ const DEFAULT_PUBLIC_KEY = web3_js_1.PublicKey.default.toBase58();
11
+ class ReferrerMap {
12
+ /**
13
+ * Creates a new UserStatsMap instance.
14
+ *
15
+ * @param {DriftClient} driftClient - The DriftClient instance.
16
+ * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
17
+ */
18
+ constructor(driftClient, bulkAccountLoader, parallelSync) {
19
+ /**
20
+ * map from authority pubkey to ReferrerInfo.
21
+ * - if a user has not been entered into the map, the value is undefined
22
+ * - if a user has no referrer, the value is null
23
+ * - if a user has a referrer, the value is a ReferrerInfo object
24
+ */
25
+ this.referrerMap = new Map();
26
+ this.driftClient = driftClient;
27
+ if (!bulkAccountLoader) {
28
+ bulkAccountLoader = new bulkAccountLoader_1.BulkAccountLoader(driftClient.connection, driftClient.opts.commitment, 0);
29
+ }
30
+ this.bulkAccountLoader = bulkAccountLoader;
31
+ this.parallelSync = parallelSync !== undefined ? parallelSync : true;
32
+ }
33
+ /**
34
+ * Subscribe to all UserStats accounts.
35
+ */
36
+ async subscribe() {
37
+ if (this.size() > 0) {
38
+ return;
39
+ }
40
+ await this.driftClient.subscribe();
41
+ await this.sync();
42
+ }
43
+ has(authorityPublicKey) {
44
+ return this.referrerMap.has(authorityPublicKey);
45
+ }
46
+ get(authorityPublicKey) {
47
+ return this.referrerMap.get(authorityPublicKey);
48
+ }
49
+ async addReferrerInfo(authority, referrerInfo) {
50
+ if (referrerInfo || referrerInfo === null) {
51
+ this.referrerMap.set(authority, referrerInfo);
52
+ }
53
+ else if (referrerInfo === undefined) {
54
+ const userStat = new userStats_1.UserStats({
55
+ driftClient: this.driftClient,
56
+ userStatsAccountPublicKey: (0, pda_1.getUserStatsAccountPublicKey)(this.driftClient.program.programId, new web3_js_1.PublicKey(authority)),
57
+ accountSubscription: {
58
+ type: 'polling',
59
+ accountLoader: this.bulkAccountLoader,
60
+ },
61
+ });
62
+ await userStat.fetchAccounts();
63
+ const newReferrerInfo = userStat.getReferrerInfo();
64
+ if (newReferrerInfo) {
65
+ this.referrerMap.set(authority.toString(), newReferrerInfo);
66
+ }
67
+ else {
68
+ this.referrerMap.set(authority.toString(), null);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Enforce that a UserStats will exist for the given authorityPublicKey,
74
+ * reading one from the blockchain if necessary.
75
+ * @param authorityPublicKey
76
+ * @returns
77
+ */
78
+ async mustGet(authorityPublicKey) {
79
+ if (!this.has(authorityPublicKey)) {
80
+ await this.addReferrerInfo(authorityPublicKey);
81
+ }
82
+ return this.get(authorityPublicKey);
83
+ }
84
+ values() {
85
+ return this.referrerMap.values();
86
+ }
87
+ size() {
88
+ return this.referrerMap.size;
89
+ }
90
+ async sync() {
91
+ if (this.fetchPromise) {
92
+ return this.fetchPromise;
93
+ }
94
+ this.fetchPromise = new Promise((resolver) => {
95
+ this.fetchPromiseResolver = resolver;
96
+ });
97
+ try {
98
+ if (this.parallelSync) {
99
+ await Promise.all([
100
+ this.syncAll(),
101
+ this.syncReferrer((0, memcmp_1.getUserStatsIsReferredFilter)()),
102
+ this.syncReferrer((0, memcmp_1.getUserStatsIsReferredOrReferrerFilter)()),
103
+ ]);
104
+ }
105
+ else {
106
+ await this.syncAll();
107
+ await this.syncReferrer((0, memcmp_1.getUserStatsIsReferredFilter)());
108
+ await this.syncReferrer((0, memcmp_1.getUserStatsIsReferredOrReferrerFilter)());
109
+ }
110
+ }
111
+ catch (e) {
112
+ console.error('error in referrerMap.sync', e);
113
+ }
114
+ finally {
115
+ this.fetchPromiseResolver();
116
+ this.fetchPromise = undefined;
117
+ }
118
+ }
119
+ async syncAll() {
120
+ const rpcRequestArgs = [
121
+ this.driftClient.program.programId.toBase58(),
122
+ {
123
+ commitment: this.driftClient.opts.commitment,
124
+ filters: [(0, memcmp_1.getUserStatsFilter)()],
125
+ encoding: 'base64',
126
+ dataSlice: {
127
+ offset: 0,
128
+ length: 0,
129
+ },
130
+ withContext: true,
131
+ },
132
+ ];
133
+ const rpcJSONResponse =
134
+ // @ts-ignore
135
+ await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
136
+ const rpcResponseAndContext = rpcJSONResponse.result;
137
+ for (const account of rpcResponseAndContext.value) {
138
+ // only add if it isn't already in the map
139
+ // so that if syncReferrer already set it, we dont overwrite
140
+ if (!this.has(account.pubkey)) {
141
+ this.addReferrerInfo(account.pubkey, null);
142
+ }
143
+ }
144
+ }
145
+ async syncReferrer(referrerFilter) {
146
+ const rpcRequestArgs = [
147
+ this.driftClient.program.programId.toBase58(),
148
+ {
149
+ commitment: this.driftClient.opts.commitment,
150
+ filters: [(0, memcmp_1.getUserStatsFilter)(), referrerFilter],
151
+ encoding: 'base64',
152
+ dataSlice: {
153
+ offset: 0,
154
+ length: 72,
155
+ },
156
+ withContext: true,
157
+ },
158
+ ];
159
+ const rpcJSONResponse =
160
+ // @ts-ignore
161
+ await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
162
+ const rpcResponseAndContext = rpcJSONResponse.result;
163
+ const batchSize = 1000;
164
+ for (let i = 0; i < rpcResponseAndContext.value.length; i += batchSize) {
165
+ const batch = rpcResponseAndContext.value.slice(i, i + batchSize);
166
+ await Promise.all(batch.map(async (programAccount) => {
167
+ // @ts-ignore
168
+ const buffer = Buffer.from(programAccount.account.data[0], programAccount.account.data[1]);
169
+ const authority = bytes_1.bs58.encode(buffer.subarray(8, 40));
170
+ const referrer = bytes_1.bs58.encode(buffer.subarray(40, 72));
171
+ const referrerKey = new web3_js_1.PublicKey(referrer);
172
+ this.addReferrerInfo(authority, referrer === DEFAULT_PUBLIC_KEY
173
+ ? null
174
+ : {
175
+ referrer: (0, pda_1.getUserAccountPublicKeySync)(this.driftClient.program.programId, referrerKey, 0),
176
+ referrerStats: (0, pda_1.getUserStatsAccountPublicKey)(this.driftClient.program.programId, referrerKey),
177
+ });
178
+ }));
179
+ await new Promise((resolve) => setTimeout(resolve, 0));
180
+ }
181
+ }
182
+ async unsubscribe() {
183
+ this.referrerMap.clear();
184
+ }
185
+ }
186
+ exports.ReferrerMap = ReferrerMap;
@@ -3880,6 +3880,9 @@ class DriftClient {
3880
3880
  const spotMarketAccount = this.getSpotMarketAccount(marketIndex);
3881
3881
  const isSolMarket = spotMarketAccount.mint.equals(spotMarkets_1.WRAPPED_SOL_MINT);
3882
3882
  const createWSOLTokenAccount = isSolMarket && collateralAccountPublicKey.equals(this.wallet.publicKey);
3883
+ // create associated token account because it may not exist
3884
+ const associatedTokenAccountPublicKey = (0, spl_token_1.getAssociatedTokenAddressSync)(spotMarketAccount.mint, this.wallet.publicKey, true);
3885
+ addIfStakeIxs.push(await (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(this.wallet.publicKey, associatedTokenAccountPublicKey, this.wallet.publicKey, spotMarketAccount.mint));
3883
3886
  let tokenAccount;
3884
3887
  if (!(await this.checkIfAccountExists(this.getUserStatsAccountPublicKey()))) {
3885
3888
  addIfStakeIxs.push(await this.getInitializeUserStatsIx());
@@ -106,6 +106,7 @@ export * from './dlob/DLOBApiClient';
106
106
  export * from './dlob/types';
107
107
  export * from './dlob/orderBookLevels';
108
108
  export * from './userMap/userMap';
109
+ export * from './userMap/referrerMap';
109
110
  export * from './userMap/userStatsMap';
110
111
  export * from './userMap/userMapConfig';
111
112
  export * from './math/bankruptcy';
package/lib/node/index.js CHANGED
@@ -129,6 +129,7 @@ __exportStar(require("./dlob/DLOBApiClient"), exports);
129
129
  __exportStar(require("./dlob/types"), exports);
130
130
  __exportStar(require("./dlob/orderBookLevels"), exports);
131
131
  __exportStar(require("./userMap/userMap"), exports);
132
+ __exportStar(require("./userMap/referrerMap"), exports);
132
133
  __exportStar(require("./userMap/userStatsMap"), exports);
133
134
  __exportStar(require("./userMap/userMapConfig"), exports);
134
135
  __exportStar(require("./math/bankruptcy"), exports);
@@ -5,3 +5,6 @@ export declare function getUserWithOrderFilter(): MemcmpFilter;
5
5
  export declare function getUserWithAuctionFilter(): MemcmpFilter;
6
6
  export declare function getUserThatHasBeenLP(): MemcmpFilter;
7
7
  export declare function getUserWithName(name: string): MemcmpFilter;
8
+ export declare function getUserStatsFilter(): MemcmpFilter;
9
+ export declare function getUserStatsIsReferredFilter(): MemcmpFilter;
10
+ export declare function getUserStatsIsReferredOrReferrerFilter(): MemcmpFilter;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getUserWithName = exports.getUserThatHasBeenLP = exports.getUserWithAuctionFilter = exports.getUserWithOrderFilter = exports.getNonIdleUserFilter = exports.getUserFilter = void 0;
6
+ exports.getUserStatsIsReferredOrReferrerFilter = exports.getUserStatsIsReferredFilter = exports.getUserStatsFilter = exports.getUserWithName = exports.getUserThatHasBeenLP = exports.getUserWithAuctionFilter = exports.getUserWithOrderFilter = exports.getNonIdleUserFilter = exports.getUserFilter = void 0;
7
7
  const bs58_1 = __importDefault(require("bs58"));
8
8
  const anchor_1 = require("@coral-xyz/anchor");
9
9
  const userName_1 = require("./userName");
@@ -61,3 +61,30 @@ function getUserWithName(name) {
61
61
  };
62
62
  }
63
63
  exports.getUserWithName = getUserWithName;
64
+ function getUserStatsFilter() {
65
+ return {
66
+ memcmp: {
67
+ offset: 0,
68
+ bytes: bs58_1.default.encode(anchor_1.BorshAccountsCoder.accountDiscriminator('UserStats')),
69
+ },
70
+ };
71
+ }
72
+ exports.getUserStatsFilter = getUserStatsFilter;
73
+ function getUserStatsIsReferredFilter() {
74
+ return {
75
+ memcmp: {
76
+ offset: 188,
77
+ bytes: bs58_1.default.encode(Buffer.from(Uint8Array.from([2]))),
78
+ },
79
+ };
80
+ }
81
+ exports.getUserStatsIsReferredFilter = getUserStatsIsReferredFilter;
82
+ function getUserStatsIsReferredOrReferrerFilter() {
83
+ return {
84
+ memcmp: {
85
+ offset: 188,
86
+ bytes: bs58_1.default.encode(Buffer.from(Uint8Array.from([3]))),
87
+ },
88
+ };
89
+ }
90
+ exports.getUserStatsIsReferredOrReferrerFilter = getUserStatsIsReferredOrReferrerFilter;
@@ -0,0 +1,45 @@
1
+ import { MemcmpFilter } from '@solana/web3.js';
2
+ import { BulkAccountLoader } from '../accounts/bulkAccountLoader';
3
+ import { DriftClient } from '../driftClient';
4
+ import { ReferrerInfo } from '../types';
5
+ export declare class ReferrerMap {
6
+ /**
7
+ * map from authority pubkey to ReferrerInfo.
8
+ * - if a user has not been entered into the map, the value is undefined
9
+ * - if a user has no referrer, the value is null
10
+ * - if a user has a referrer, the value is a ReferrerInfo object
11
+ */
12
+ private referrerMap;
13
+ private driftClient;
14
+ private bulkAccountLoader;
15
+ private parallelSync;
16
+ private fetchPromise?;
17
+ private fetchPromiseResolver;
18
+ /**
19
+ * Creates a new UserStatsMap instance.
20
+ *
21
+ * @param {DriftClient} driftClient - The DriftClient instance.
22
+ * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
23
+ */
24
+ constructor(driftClient: DriftClient, bulkAccountLoader?: BulkAccountLoader, parallelSync?: boolean);
25
+ /**
26
+ * Subscribe to all UserStats accounts.
27
+ */
28
+ subscribe(): Promise<void>;
29
+ has(authorityPublicKey: string): boolean;
30
+ get(authorityPublicKey: string): ReferrerInfo | undefined | null;
31
+ addReferrerInfo(authority: string, referrerInfo?: ReferrerInfo | null): Promise<void>;
32
+ /**
33
+ * Enforce that a UserStats will exist for the given authorityPublicKey,
34
+ * reading one from the blockchain if necessary.
35
+ * @param authorityPublicKey
36
+ * @returns
37
+ */
38
+ mustGet(authorityPublicKey: string): Promise<ReferrerInfo | null | undefined>;
39
+ values(): IterableIterator<ReferrerInfo | null>;
40
+ size(): number;
41
+ sync(): Promise<void>;
42
+ syncAll(): Promise<void>;
43
+ syncReferrer(referrerFilter: MemcmpFilter): Promise<void>;
44
+ unsubscribe(): Promise<void>;
45
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReferrerMap = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const bulkAccountLoader_1 = require("../accounts/bulkAccountLoader");
6
+ const userStats_1 = require("../userStats");
7
+ const pda_1 = require("../addresses/pda");
8
+ const memcmp_1 = require("../memcmp");
9
+ const bytes_1 = require("@coral-xyz/anchor/dist/cjs/utils/bytes");
10
+ const DEFAULT_PUBLIC_KEY = web3_js_1.PublicKey.default.toBase58();
11
+ class ReferrerMap {
12
+ /**
13
+ * Creates a new UserStatsMap instance.
14
+ *
15
+ * @param {DriftClient} driftClient - The DriftClient instance.
16
+ * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
17
+ */
18
+ constructor(driftClient, bulkAccountLoader, parallelSync) {
19
+ /**
20
+ * map from authority pubkey to ReferrerInfo.
21
+ * - if a user has not been entered into the map, the value is undefined
22
+ * - if a user has no referrer, the value is null
23
+ * - if a user has a referrer, the value is a ReferrerInfo object
24
+ */
25
+ this.referrerMap = new Map();
26
+ this.driftClient = driftClient;
27
+ if (!bulkAccountLoader) {
28
+ bulkAccountLoader = new bulkAccountLoader_1.BulkAccountLoader(driftClient.connection, driftClient.opts.commitment, 0);
29
+ }
30
+ this.bulkAccountLoader = bulkAccountLoader;
31
+ this.parallelSync = parallelSync !== undefined ? parallelSync : true;
32
+ }
33
+ /**
34
+ * Subscribe to all UserStats accounts.
35
+ */
36
+ async subscribe() {
37
+ if (this.size() > 0) {
38
+ return;
39
+ }
40
+ await this.driftClient.subscribe();
41
+ await this.sync();
42
+ }
43
+ has(authorityPublicKey) {
44
+ return this.referrerMap.has(authorityPublicKey);
45
+ }
46
+ get(authorityPublicKey) {
47
+ return this.referrerMap.get(authorityPublicKey);
48
+ }
49
+ async addReferrerInfo(authority, referrerInfo) {
50
+ if (referrerInfo || referrerInfo === null) {
51
+ this.referrerMap.set(authority, referrerInfo);
52
+ }
53
+ else if (referrerInfo === undefined) {
54
+ const userStat = new userStats_1.UserStats({
55
+ driftClient: this.driftClient,
56
+ userStatsAccountPublicKey: (0, pda_1.getUserStatsAccountPublicKey)(this.driftClient.program.programId, new web3_js_1.PublicKey(authority)),
57
+ accountSubscription: {
58
+ type: 'polling',
59
+ accountLoader: this.bulkAccountLoader,
60
+ },
61
+ });
62
+ await userStat.fetchAccounts();
63
+ const newReferrerInfo = userStat.getReferrerInfo();
64
+ if (newReferrerInfo) {
65
+ this.referrerMap.set(authority.toString(), newReferrerInfo);
66
+ }
67
+ else {
68
+ this.referrerMap.set(authority.toString(), null);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Enforce that a UserStats will exist for the given authorityPublicKey,
74
+ * reading one from the blockchain if necessary.
75
+ * @param authorityPublicKey
76
+ * @returns
77
+ */
78
+ async mustGet(authorityPublicKey) {
79
+ if (!this.has(authorityPublicKey)) {
80
+ await this.addReferrerInfo(authorityPublicKey);
81
+ }
82
+ return this.get(authorityPublicKey);
83
+ }
84
+ values() {
85
+ return this.referrerMap.values();
86
+ }
87
+ size() {
88
+ return this.referrerMap.size;
89
+ }
90
+ async sync() {
91
+ if (this.fetchPromise) {
92
+ return this.fetchPromise;
93
+ }
94
+ this.fetchPromise = new Promise((resolver) => {
95
+ this.fetchPromiseResolver = resolver;
96
+ });
97
+ try {
98
+ if (this.parallelSync) {
99
+ await Promise.all([
100
+ this.syncAll(),
101
+ this.syncReferrer((0, memcmp_1.getUserStatsIsReferredFilter)()),
102
+ this.syncReferrer((0, memcmp_1.getUserStatsIsReferredOrReferrerFilter)()),
103
+ ]);
104
+ }
105
+ else {
106
+ await this.syncAll();
107
+ await this.syncReferrer((0, memcmp_1.getUserStatsIsReferredFilter)());
108
+ await this.syncReferrer((0, memcmp_1.getUserStatsIsReferredOrReferrerFilter)());
109
+ }
110
+ }
111
+ catch (e) {
112
+ console.error('error in referrerMap.sync', e);
113
+ }
114
+ finally {
115
+ this.fetchPromiseResolver();
116
+ this.fetchPromise = undefined;
117
+ }
118
+ }
119
+ async syncAll() {
120
+ const rpcRequestArgs = [
121
+ this.driftClient.program.programId.toBase58(),
122
+ {
123
+ commitment: this.driftClient.opts.commitment,
124
+ filters: [(0, memcmp_1.getUserStatsFilter)()],
125
+ encoding: 'base64',
126
+ dataSlice: {
127
+ offset: 0,
128
+ length: 0,
129
+ },
130
+ withContext: true,
131
+ },
132
+ ];
133
+ const rpcJSONResponse =
134
+ // @ts-ignore
135
+ await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
136
+ const rpcResponseAndContext = rpcJSONResponse.result;
137
+ for (const account of rpcResponseAndContext.value) {
138
+ // only add if it isn't already in the map
139
+ // so that if syncReferrer already set it, we dont overwrite
140
+ if (!this.has(account.pubkey)) {
141
+ this.addReferrerInfo(account.pubkey, null);
142
+ }
143
+ }
144
+ }
145
+ async syncReferrer(referrerFilter) {
146
+ const rpcRequestArgs = [
147
+ this.driftClient.program.programId.toBase58(),
148
+ {
149
+ commitment: this.driftClient.opts.commitment,
150
+ filters: [(0, memcmp_1.getUserStatsFilter)(), referrerFilter],
151
+ encoding: 'base64',
152
+ dataSlice: {
153
+ offset: 0,
154
+ length: 72,
155
+ },
156
+ withContext: true,
157
+ },
158
+ ];
159
+ const rpcJSONResponse =
160
+ // @ts-ignore
161
+ await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
162
+ const rpcResponseAndContext = rpcJSONResponse.result;
163
+ const batchSize = 1000;
164
+ for (let i = 0; i < rpcResponseAndContext.value.length; i += batchSize) {
165
+ const batch = rpcResponseAndContext.value.slice(i, i + batchSize);
166
+ await Promise.all(batch.map(async (programAccount) => {
167
+ // @ts-ignore
168
+ const buffer = Buffer.from(programAccount.account.data[0], programAccount.account.data[1]);
169
+ const authority = bytes_1.bs58.encode(buffer.subarray(8, 40));
170
+ const referrer = bytes_1.bs58.encode(buffer.subarray(40, 72));
171
+ const referrerKey = new web3_js_1.PublicKey(referrer);
172
+ this.addReferrerInfo(authority, referrer === DEFAULT_PUBLIC_KEY
173
+ ? null
174
+ : {
175
+ referrer: (0, pda_1.getUserAccountPublicKeySync)(this.driftClient.program.programId, referrerKey, 0),
176
+ referrerStats: (0, pda_1.getUserStatsAccountPublicKey)(this.driftClient.program.programId, referrerKey),
177
+ });
178
+ }));
179
+ await new Promise((resolve) => setTimeout(resolve, 0));
180
+ }
181
+ }
182
+ async unsubscribe() {
183
+ this.referrerMap.clear();
184
+ }
185
+ }
186
+ exports.ReferrerMap = ReferrerMap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.98.0-beta.11",
3
+ "version": "2.98.0-beta.13",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "browser": "./lib/browser/index.js",
@@ -11,11 +11,13 @@ import bs58 from 'bs58';
11
11
  import {
12
12
  ASSOCIATED_TOKEN_PROGRAM_ID,
13
13
  createAssociatedTokenAccountInstruction,
14
+ createAssociatedTokenAccountIdempotentInstruction,
14
15
  createCloseAccountInstruction,
15
16
  createInitializeAccountInstruction,
16
17
  getAssociatedTokenAddress,
17
18
  TOKEN_2022_PROGRAM_ID,
18
19
  TOKEN_PROGRAM_ID,
20
+ getAssociatedTokenAddressSync,
19
21
  } from '@solana/spl-token';
20
22
  import {
21
23
  DriftClientMetricsEvents,
@@ -7407,6 +7409,22 @@ export class DriftClient {
7407
7409
  const createWSOLTokenAccount =
7408
7410
  isSolMarket && collateralAccountPublicKey.equals(this.wallet.publicKey);
7409
7411
 
7412
+ // create associated token account because it may not exist
7413
+ const associatedTokenAccountPublicKey = getAssociatedTokenAddressSync(
7414
+ spotMarketAccount.mint,
7415
+ this.wallet.publicKey,
7416
+ true
7417
+ );
7418
+
7419
+ addIfStakeIxs.push(
7420
+ await createAssociatedTokenAccountIdempotentInstruction(
7421
+ this.wallet.publicKey,
7422
+ associatedTokenAccountPublicKey,
7423
+ this.wallet.publicKey,
7424
+ spotMarketAccount.mint
7425
+ )
7426
+ );
7427
+
7410
7428
  let tokenAccount;
7411
7429
 
7412
7430
  if (
package/src/index.ts CHANGED
@@ -107,6 +107,7 @@ export * from './dlob/DLOBApiClient';
107
107
  export * from './dlob/types';
108
108
  export * from './dlob/orderBookLevels';
109
109
  export * from './userMap/userMap';
110
+ export * from './userMap/referrerMap';
110
111
  export * from './userMap/userStatsMap';
111
112
  export * from './userMap/userMapConfig';
112
113
  export * from './math/bankruptcy';
package/src/memcmp.ts CHANGED
@@ -56,3 +56,30 @@ export function getUserWithName(name: string): MemcmpFilter {
56
56
  },
57
57
  };
58
58
  }
59
+
60
+ export function getUserStatsFilter(): MemcmpFilter {
61
+ return {
62
+ memcmp: {
63
+ offset: 0,
64
+ bytes: bs58.encode(BorshAccountsCoder.accountDiscriminator('UserStats')),
65
+ },
66
+ };
67
+ }
68
+
69
+ export function getUserStatsIsReferredFilter(): MemcmpFilter {
70
+ return {
71
+ memcmp: {
72
+ offset: 188,
73
+ bytes: bs58.encode(Buffer.from(Uint8Array.from([2]))),
74
+ },
75
+ };
76
+ }
77
+
78
+ export function getUserStatsIsReferredOrReferrerFilter(): MemcmpFilter {
79
+ return {
80
+ memcmp: {
81
+ offset: 188,
82
+ bytes: bs58.encode(Buffer.from(Uint8Array.from([3]))),
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,273 @@
1
+ import {
2
+ MemcmpFilter,
3
+ PublicKey,
4
+ RpcResponseAndContext,
5
+ } from '@solana/web3.js';
6
+ import { BulkAccountLoader } from '../accounts/bulkAccountLoader';
7
+ import { DriftClient } from '../driftClient';
8
+ import { ReferrerInfo } from '../types';
9
+ import { UserStats } from '../userStats';
10
+ import {
11
+ getUserAccountPublicKeySync,
12
+ getUserStatsAccountPublicKey,
13
+ } from '../addresses/pda';
14
+ import {
15
+ getUserStatsFilter,
16
+ getUserStatsIsReferredFilter,
17
+ getUserStatsIsReferredOrReferrerFilter,
18
+ } from '../memcmp';
19
+ import { bs58 } from '@coral-xyz/anchor/dist/cjs/utils/bytes';
20
+
21
+ const DEFAULT_PUBLIC_KEY = PublicKey.default.toBase58();
22
+
23
+ export class ReferrerMap {
24
+ /**
25
+ * map from authority pubkey to ReferrerInfo.
26
+ * - if a user has not been entered into the map, the value is undefined
27
+ * - if a user has no referrer, the value is null
28
+ * - if a user has a referrer, the value is a ReferrerInfo object
29
+ */
30
+ private referrerMap = new Map<string, ReferrerInfo | null>();
31
+ private driftClient: DriftClient;
32
+ private bulkAccountLoader: BulkAccountLoader;
33
+ private parallelSync: boolean;
34
+
35
+ private fetchPromise?: Promise<void>;
36
+ private fetchPromiseResolver: () => void;
37
+
38
+ /**
39
+ * Creates a new UserStatsMap instance.
40
+ *
41
+ * @param {DriftClient} driftClient - The DriftClient instance.
42
+ * @param {BulkAccountLoader} [bulkAccountLoader] - If not provided, a new BulkAccountLoader with polling disabled will be created.
43
+ */
44
+ constructor(
45
+ driftClient: DriftClient,
46
+ bulkAccountLoader?: BulkAccountLoader,
47
+ parallelSync?: boolean
48
+ ) {
49
+ this.driftClient = driftClient;
50
+ if (!bulkAccountLoader) {
51
+ bulkAccountLoader = new BulkAccountLoader(
52
+ driftClient.connection,
53
+ driftClient.opts.commitment,
54
+ 0
55
+ );
56
+ }
57
+ this.bulkAccountLoader = bulkAccountLoader;
58
+ this.parallelSync = parallelSync !== undefined ? parallelSync : true;
59
+ }
60
+
61
+ /**
62
+ * Subscribe to all UserStats accounts.
63
+ */
64
+ public async subscribe() {
65
+ if (this.size() > 0) {
66
+ return;
67
+ }
68
+
69
+ await this.driftClient.subscribe();
70
+ await this.sync();
71
+ }
72
+
73
+ public has(authorityPublicKey: string): boolean {
74
+ return this.referrerMap.has(authorityPublicKey);
75
+ }
76
+
77
+ public get(authorityPublicKey: string): ReferrerInfo | undefined | null {
78
+ return this.referrerMap.get(authorityPublicKey);
79
+ }
80
+
81
+ public async addReferrerInfo(
82
+ authority: string,
83
+ referrerInfo?: ReferrerInfo | null
84
+ ) {
85
+ if (referrerInfo || referrerInfo === null) {
86
+ this.referrerMap.set(authority, referrerInfo);
87
+ } else if (referrerInfo === undefined) {
88
+ const userStat = new UserStats({
89
+ driftClient: this.driftClient,
90
+ userStatsAccountPublicKey: getUserStatsAccountPublicKey(
91
+ this.driftClient.program.programId,
92
+ new PublicKey(authority)
93
+ ),
94
+ accountSubscription: {
95
+ type: 'polling',
96
+ accountLoader: this.bulkAccountLoader,
97
+ },
98
+ });
99
+ await userStat.fetchAccounts();
100
+
101
+ const newReferrerInfo = userStat.getReferrerInfo();
102
+
103
+ if (newReferrerInfo) {
104
+ this.referrerMap.set(authority.toString(), newReferrerInfo);
105
+ } else {
106
+ this.referrerMap.set(authority.toString(), null);
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Enforce that a UserStats will exist for the given authorityPublicKey,
113
+ * reading one from the blockchain if necessary.
114
+ * @param authorityPublicKey
115
+ * @returns
116
+ */
117
+ public async mustGet(
118
+ authorityPublicKey: string
119
+ ): Promise<ReferrerInfo | null | undefined> {
120
+ if (!this.has(authorityPublicKey)) {
121
+ await this.addReferrerInfo(authorityPublicKey);
122
+ }
123
+ return this.get(authorityPublicKey);
124
+ }
125
+
126
+ public values(): IterableIterator<ReferrerInfo | null> {
127
+ return this.referrerMap.values();
128
+ }
129
+
130
+ public size(): number {
131
+ return this.referrerMap.size;
132
+ }
133
+
134
+ public async sync(): Promise<void> {
135
+ if (this.fetchPromise) {
136
+ return this.fetchPromise;
137
+ }
138
+
139
+ this.fetchPromise = new Promise((resolver) => {
140
+ this.fetchPromiseResolver = resolver;
141
+ });
142
+
143
+ try {
144
+ if (this.parallelSync) {
145
+ await Promise.all([
146
+ this.syncAll(),
147
+ this.syncReferrer(getUserStatsIsReferredFilter()),
148
+ this.syncReferrer(getUserStatsIsReferredOrReferrerFilter()),
149
+ ]);
150
+ } else {
151
+ await this.syncAll();
152
+ await this.syncReferrer(getUserStatsIsReferredFilter());
153
+ await this.syncReferrer(getUserStatsIsReferredOrReferrerFilter());
154
+ }
155
+ } catch (e) {
156
+ console.error('error in referrerMap.sync', e);
157
+ } finally {
158
+ this.fetchPromiseResolver();
159
+ this.fetchPromise = undefined;
160
+ }
161
+ }
162
+
163
+ public async syncAll(): Promise<void> {
164
+ const rpcRequestArgs = [
165
+ this.driftClient.program.programId.toBase58(),
166
+ {
167
+ commitment: this.driftClient.opts.commitment,
168
+ filters: [getUserStatsFilter()],
169
+ encoding: 'base64',
170
+ dataSlice: {
171
+ offset: 0,
172
+ length: 0,
173
+ },
174
+ withContext: true,
175
+ },
176
+ ];
177
+
178
+ const rpcJSONResponse: any =
179
+ // @ts-ignore
180
+ await this.driftClient.connection._rpcRequest(
181
+ 'getProgramAccounts',
182
+ rpcRequestArgs
183
+ );
184
+
185
+ const rpcResponseAndContext: RpcResponseAndContext<
186
+ Array<{
187
+ pubkey: string;
188
+ account: {
189
+ data: [string, string];
190
+ };
191
+ }>
192
+ > = rpcJSONResponse.result;
193
+
194
+ for (const account of rpcResponseAndContext.value) {
195
+ // only add if it isn't already in the map
196
+ // so that if syncReferrer already set it, we dont overwrite
197
+ if (!this.has(account.pubkey)) {
198
+ this.addReferrerInfo(account.pubkey, null);
199
+ }
200
+ }
201
+ }
202
+
203
+ async syncReferrer(referrerFilter: MemcmpFilter): Promise<void> {
204
+ const rpcRequestArgs = [
205
+ this.driftClient.program.programId.toBase58(),
206
+ {
207
+ commitment: this.driftClient.opts.commitment,
208
+ filters: [getUserStatsFilter(), referrerFilter],
209
+ encoding: 'base64',
210
+ dataSlice: {
211
+ offset: 0,
212
+ length: 72,
213
+ },
214
+ withContext: true,
215
+ },
216
+ ];
217
+
218
+ const rpcJSONResponse: any =
219
+ // @ts-ignore
220
+ await this.driftClient.connection._rpcRequest(
221
+ 'getProgramAccounts',
222
+ rpcRequestArgs
223
+ );
224
+
225
+ const rpcResponseAndContext: RpcResponseAndContext<
226
+ Array<{
227
+ pubkey: string;
228
+ account: {
229
+ data: [string, string];
230
+ };
231
+ }>
232
+ > = rpcJSONResponse.result;
233
+
234
+ const batchSize = 1000;
235
+ for (let i = 0; i < rpcResponseAndContext.value.length; i += batchSize) {
236
+ const batch = rpcResponseAndContext.value.slice(i, i + batchSize);
237
+ await Promise.all(
238
+ batch.map(async (programAccount) => {
239
+ // @ts-ignore
240
+ const buffer = Buffer.from(
241
+ programAccount.account.data[0],
242
+ programAccount.account.data[1]
243
+ );
244
+ const authority = bs58.encode(buffer.subarray(8, 40));
245
+ const referrer = bs58.encode(buffer.subarray(40, 72));
246
+
247
+ const referrerKey = new PublicKey(referrer);
248
+ this.addReferrerInfo(
249
+ authority,
250
+ referrer === DEFAULT_PUBLIC_KEY
251
+ ? null
252
+ : {
253
+ referrer: getUserAccountPublicKeySync(
254
+ this.driftClient.program.programId,
255
+ referrerKey,
256
+ 0
257
+ ),
258
+ referrerStats: getUserStatsAccountPublicKey(
259
+ this.driftClient.program.programId,
260
+ referrerKey
261
+ ),
262
+ }
263
+ );
264
+ })
265
+ );
266
+ await new Promise((resolve) => setTimeout(resolve, 0));
267
+ }
268
+ }
269
+
270
+ public async unsubscribe() {
271
+ this.referrerMap.clear();
272
+ }
273
+ }