@drift-labs/sdk 2.149.1 → 2.150.0-alpha.0

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 (58) hide show
  1. package/.env +4 -0
  2. package/VERSION +1 -1
  3. package/lib/browser/constants/perpMarkets.js +11 -0
  4. package/lib/browser/constants/spotMarkets.js +13 -0
  5. package/lib/browser/decode/user.js +2 -2
  6. package/lib/browser/driftClient.d.ts +20 -8
  7. package/lib/browser/driftClient.js +216 -17
  8. package/lib/browser/idl/drift.json +225 -21
  9. package/lib/browser/math/margin.js +2 -1
  10. package/lib/browser/math/position.d.ts +1 -0
  11. package/lib/browser/math/position.js +10 -2
  12. package/lib/browser/math/superStake.d.ts +3 -2
  13. package/lib/browser/types.d.ts +12 -6
  14. package/lib/browser/types.js +11 -6
  15. package/lib/browser/user.d.ts +3 -2
  16. package/lib/browser/user.js +24 -8
  17. package/lib/node/constants/perpMarkets.d.ts.map +1 -1
  18. package/lib/node/constants/perpMarkets.js +11 -0
  19. package/lib/node/constants/spotMarkets.d.ts.map +1 -1
  20. package/lib/node/constants/spotMarkets.js +13 -0
  21. package/lib/node/decode/user.d.ts.map +1 -1
  22. package/lib/node/decode/user.js +2 -2
  23. package/lib/node/driftClient.d.ts +20 -8
  24. package/lib/node/driftClient.d.ts.map +1 -1
  25. package/lib/node/driftClient.js +216 -17
  26. package/lib/node/idl/drift.json +225 -21
  27. package/lib/node/math/margin.d.ts.map +1 -1
  28. package/lib/node/math/margin.js +2 -1
  29. package/lib/node/math/position.d.ts +1 -0
  30. package/lib/node/math/position.d.ts.map +1 -1
  31. package/lib/node/math/position.js +10 -2
  32. package/lib/node/math/spotBalance.d.ts.map +1 -1
  33. package/lib/node/math/superStake.d.ts +3 -2
  34. package/lib/node/math/superStake.d.ts.map +1 -1
  35. package/lib/node/types.d.ts +12 -6
  36. package/lib/node/types.d.ts.map +1 -1
  37. package/lib/node/types.js +11 -6
  38. package/lib/node/user.d.ts +3 -2
  39. package/lib/node/user.d.ts.map +1 -1
  40. package/lib/node/user.js +24 -8
  41. package/package.json +1 -1
  42. package/scripts/deposit-isolated-positions.ts +110 -0
  43. package/scripts/single-grpc-client-test.ts +71 -21
  44. package/scripts/withdraw-isolated-positions.ts +174 -0
  45. package/src/constants/perpMarkets.ts +11 -0
  46. package/src/constants/spotMarkets.ts +14 -0
  47. package/src/decode/user.ts +2 -3
  48. package/src/driftClient.ts +464 -41
  49. package/src/idl/drift.json +226 -22
  50. package/src/margin/README.md +143 -0
  51. package/src/math/margin.ts +3 -4
  52. package/src/math/position.ts +12 -2
  53. package/src/math/spotBalance.ts +0 -1
  54. package/src/types.ts +15 -7
  55. package/src/user.ts +49 -15
  56. package/tests/amm/test.ts +1 -1
  57. package/tests/dlob/helpers.ts +1 -1
  58. package/tests/user/test.ts +0 -7
@@ -48,9 +48,7 @@ async function initializeSingleGrpcClient() {
48
48
  const allPerpMarketProgramAccounts =
49
49
  (await program.account.perpMarket.all()) as ProgramAccount<PerpMarketAccount>[];
50
50
  const perpMarketProgramAccounts = allPerpMarketProgramAccounts.filter((val) =>
51
- [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].includes(
52
- val.account.marketIndex
53
- )
51
+ [46].includes(val.account.marketIndex)
54
52
  );
55
53
  const perpMarketIndexes = perpMarketProgramAccounts.map(
56
54
  (val) => val.account.marketIndex
@@ -60,7 +58,7 @@ async function initializeSingleGrpcClient() {
60
58
  const allSpotMarketProgramAccounts =
61
59
  (await program.account.spotMarket.all()) as ProgramAccount<SpotMarketAccount>[];
62
60
  const spotMarketProgramAccounts = allSpotMarketProgramAccounts.filter((val) =>
63
- [0, 1, 2, 3, 4, 5].includes(val.account.marketIndex)
61
+ [0].includes(val.account.marketIndex)
64
62
  );
65
63
  const spotMarketIndexes = spotMarketProgramAccounts.map(
66
64
  (val) => val.account.marketIndex
@@ -94,7 +92,9 @@ async function initializeSingleGrpcClient() {
94
92
  }
95
93
  }
96
94
 
97
- console.log(`📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`);
95
+ console.log(
96
+ `📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`
97
+ );
98
98
  console.log(`🔮 Oracles: ${oracleInfos.length}`);
99
99
 
100
100
 
@@ -171,7 +171,9 @@ async function initializeSingleGrpcClient() {
171
171
  await client.subscribe();
172
172
 
173
173
  console.log('✅ Client subscribed successfully!');
174
- console.log('🚀 Starting high-load testing (50 reads/sec per perp market)...');
174
+ console.log(
175
+ '🚀 Starting high-load testing (50 reads/sec per perp market)...'
176
+ );
175
177
 
176
178
  // High-frequency load testing - 50 reads per second per perp market
177
179
  const loadTestInterval = setInterval(async () => {
@@ -179,29 +181,71 @@ async function initializeSingleGrpcClient() {
179
181
  // Test getPerpMarketAccount for each perp market (50 times per second per market)
180
182
  for (const marketIndex of perpMarketIndexes) {
181
183
  const perpMarketAccount = client.getPerpMarketAccount(marketIndex);
182
- console.log("perpMarketAccount name: ", decodeName(perpMarketAccount.name));
183
- console.log("perpMarketAccount data: ", JSON.stringify({
184
- marketIndex: perpMarketAccount.marketIndex,
185
- name: decodeName(perpMarketAccount.name),
186
- baseAssetReserve: perpMarketAccount.amm.baseAssetReserve.toString(),
187
- quoteAssetReserve: perpMarketAccount.amm.quoteAssetReserve.toString()
188
- }));
184
+ if (!perpMarketAccount) {
185
+ console.log(`Perp market ${marketIndex} not found`);
186
+ continue;
187
+ }
188
+ console.log(
189
+ 'perpMarketAccount name: ',
190
+ decodeName(perpMarketAccount.name)
191
+ );
192
+ console.log(
193
+ 'perpMarketAccount data: ',
194
+ JSON.stringify({
195
+ marketIndex: perpMarketAccount.marketIndex,
196
+ name: decodeName(perpMarketAccount.name),
197
+ baseAssetReserve: perpMarketAccount.amm.baseAssetReserve.toString(),
198
+ quoteAssetReserve:
199
+ perpMarketAccount.amm.quoteAssetReserve.toString(),
200
+ })
201
+ );
189
202
  }
190
203
 
191
204
  // Test getMMOracleDataForPerpMarket for each perp market (50 times per second per market)
192
205
  for (const marketIndex of perpMarketIndexes) {
193
206
  try {
194
207
  const oracleData = client.getMMOracleDataForPerpMarket(marketIndex);
195
- console.log("oracleData price: ", oracleData.price.toString());
196
- console.log("oracleData: ", JSON.stringify({
197
- price: oracleData.price.toString(),
198
- confidence: oracleData.confidence?.toString(),
199
- slot: oracleData.slot?.toString()
200
- }));
208
+ console.log('oracleData price: ', oracleData.price.toString());
209
+ console.log(
210
+ 'oracleData: ',
211
+ JSON.stringify({
212
+ price: oracleData.price.toString(),
213
+ confidence: oracleData.confidence?.toString(),
214
+ slot: oracleData.slot?.toString(),
215
+ })
216
+ );
201
217
  } catch (error) {
202
218
  // Ignore errors for load testing
203
219
  }
204
220
  }
221
+
222
+ for (const marketIndex of perpMarketIndexes) {
223
+ try {
224
+ const { data, slot } =
225
+ client.accountSubscriber.getMarketAccountAndSlot(marketIndex);
226
+ if (!data) {
227
+ console.log(
228
+ `Perp market getMarketAccountAndSlot ${marketIndex} not found`
229
+ );
230
+ continue;
231
+ }
232
+ console.log(
233
+ 'marketAccountAndSlot: ',
234
+ JSON.stringify({
235
+ marketIndex: data.marketIndex,
236
+ name: decodeName(data.name),
237
+ slot: slot?.toString(),
238
+ baseAssetReserve: data.amm.baseAssetReserve.toString(),
239
+ quoteAssetReserve: data.amm.quoteAssetReserve.toString(),
240
+ })
241
+ );
242
+ } catch (error) {
243
+ console.error(
244
+ `Error getting market account and slot for market ${marketIndex}:`,
245
+ error
246
+ );
247
+ }
248
+ }
205
249
  } catch (error) {
206
250
  console.error('Load test error:', error);
207
251
  }
@@ -211,8 +255,14 @@ async function initializeSingleGrpcClient() {
211
255
  const statsInterval = setInterval(() => {
212
256
  console.log('\n📈 Event Counts:', eventCounts);
213
257
  console.log(`⏱️ Client subscribed: ${client.isSubscribed}`);
214
- console.log(`🔗 Account subscriber subscribed: ${client.accountSubscriber.isSubscribed}`);
215
- console.log(`🔥 Load: ${perpMarketIndexes.length * 50 * 2} reads/sec (${perpMarketIndexes.length} markets × 50 getPerpMarketAccount + 50 getMMOracleDataForPerpMarket)`);
258
+ console.log(
259
+ `🔗 Account subscriber subscribed: ${client.accountSubscriber.isSubscribed}`
260
+ );
261
+ console.log(
262
+ `🔥 Load: ${perpMarketIndexes.length * 50 * 2} reads/sec (${
263
+ perpMarketIndexes.length
264
+ } markets × 50 getPerpMarketAccount + 50 getMMOracleDataForPerpMarket)`
265
+ );
216
266
  }, 5000);
217
267
 
218
268
  // Handle shutdown signals - just exit without cleanup since they never unsubscribe
@@ -0,0 +1,174 @@
1
+ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
2
+ import dotenv from 'dotenv';
3
+ import {
4
+ AnchorProvider,
5
+ Idl,
6
+ Program,
7
+ ProgramAccount,
8
+ } from '@coral-xyz/anchor';
9
+ import driftIDL from '../src/idl/drift.json';
10
+ import {
11
+ DRIFT_PROGRAM_ID,
12
+ PerpMarketAccount,
13
+ SpotMarketAccount,
14
+ OracleInfo,
15
+ Wallet,
16
+ ZERO,
17
+ } from '../src';
18
+ import { DriftClient } from '../src/driftClient';
19
+ import { DriftClientConfig } from '../src/driftClientConfig';
20
+
21
+ function isStatusOpen(status: any) {
22
+ return !!status && 'open' in status;
23
+ }
24
+
25
+ function isPerpMarketType(marketType: any) {
26
+ return !!marketType && 'perp' in marketType;
27
+ }
28
+
29
+ async function main() {
30
+ dotenv.config({ path: '../' });
31
+
32
+ const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
33
+ if (!RPC_ENDPOINT) throw new Error('RPC_ENDPOINT env var required');
34
+
35
+ // Load wallet
36
+ // For safety this creates a new ephemeral wallet unless PRIVATE_KEY is provided (base58 array)
37
+ let keypair: Keypair;
38
+ const pk = process.env.PRIVATE_KEY;
39
+ if (pk) {
40
+ const secret = Uint8Array.from(JSON.parse(pk));
41
+ keypair = Keypair.fromSecretKey(secret);
42
+ } else {
43
+ keypair = new Keypair();
44
+ console.warn(
45
+ 'Using ephemeral keypair. Provide PRIVATE_KEY for real withdrawals.'
46
+ );
47
+ }
48
+ const wallet = new Wallet(keypair);
49
+
50
+ // Connection and program for market discovery
51
+ const connection = new Connection(RPC_ENDPOINT);
52
+ const provider = new AnchorProvider(connection, wallet as any, {
53
+ commitment: 'processed',
54
+ });
55
+ const programId = new PublicKey(DRIFT_PROGRAM_ID);
56
+ const program = new Program(driftIDL as Idl, programId, provider);
57
+
58
+ // Discover markets and oracles (like the example test script)
59
+ const allPerpMarketProgramAccounts =
60
+ (await program.account.perpMarket.all()) as ProgramAccount<PerpMarketAccount>[];
61
+ const perpMarketIndexes = allPerpMarketProgramAccounts.map(
62
+ (val) => val.account.marketIndex
63
+ );
64
+ const allSpotMarketProgramAccounts =
65
+ (await program.account.spotMarket.all()) as ProgramAccount<SpotMarketAccount>[];
66
+ const spotMarketIndexes = allSpotMarketProgramAccounts.map(
67
+ (val) => val.account.marketIndex
68
+ );
69
+
70
+ const seen = new Set<string>();
71
+ const oracleInfos: OracleInfo[] = [];
72
+ for (const acct of allPerpMarketProgramAccounts) {
73
+ const key = `${acct.account.amm.oracle.toBase58()}-${
74
+ Object.keys(acct.account.amm.oracleSource)[0]
75
+ }`;
76
+ if (!seen.has(key)) {
77
+ seen.add(key);
78
+ oracleInfos.push({
79
+ publicKey: acct.account.amm.oracle,
80
+ source: acct.account.amm.oracleSource,
81
+ });
82
+ }
83
+ }
84
+ for (const acct of allSpotMarketProgramAccounts) {
85
+ const key = `${acct.account.oracle.toBase58()}-${
86
+ Object.keys(acct.account.oracleSource)[0]
87
+ }`;
88
+ if (!seen.has(key)) {
89
+ seen.add(key);
90
+ oracleInfos.push({
91
+ publicKey: acct.account.oracle,
92
+ source: acct.account.oracleSource,
93
+ });
94
+ }
95
+ }
96
+
97
+ // Build DriftClient with websocket subscription (lightweight)
98
+ const clientConfig: DriftClientConfig = {
99
+ connection,
100
+ wallet,
101
+ programID: programId,
102
+ accountSubscription: {
103
+ type: 'websocket',
104
+ commitment: 'processed',
105
+ },
106
+ perpMarketIndexes,
107
+ spotMarketIndexes,
108
+ oracleInfos,
109
+ env: 'devnet',
110
+ };
111
+ const client = new DriftClient(clientConfig);
112
+ await client.subscribe();
113
+
114
+ // Ensure user exists and is subscribed
115
+ const user = client.getUser();
116
+ await user.subscribe();
117
+
118
+ const userAccount = user.getUserAccount();
119
+ const openOrders = user.getOpenOrders();
120
+
121
+ const marketsWithOpenOrders = new Set<number>();
122
+ for (const o of openOrders ?? []) {
123
+ if (isStatusOpen(o.status) && isPerpMarketType(o.marketType)) {
124
+ marketsWithOpenOrders.add(o.marketIndex);
125
+ }
126
+ }
127
+
128
+ const withdrawTargets = userAccount.perpPositions.filter((pos) => {
129
+ const isZeroBase = pos.baseAssetAmount.eq(ZERO);
130
+ const hasIso = pos.isolatedPositionScaledBalance.gt(ZERO);
131
+ const hasOpenOrders = marketsWithOpenOrders.has(pos.marketIndex);
132
+ return isZeroBase && hasIso && !hasOpenOrders;
133
+ });
134
+
135
+ console.log(
136
+ `Found ${withdrawTargets.length} isolated perp positions to withdraw`
137
+ );
138
+
139
+ for (const pos of withdrawTargets) {
140
+ try {
141
+ const amount = client.getIsolatedPerpPositionTokenAmount(pos.marketIndex);
142
+ if (amount.lte(ZERO)) continue;
143
+
144
+ const perpMarketAccount = client.getPerpMarketAccount(pos.marketIndex);
145
+ const quoteAta = await client.getAssociatedTokenAccount(
146
+ perpMarketAccount.quoteSpotMarketIndex
147
+ );
148
+
149
+ const ixs = await client.getWithdrawFromIsolatedPerpPositionIxsBundle(
150
+ amount,
151
+ pos.marketIndex,
152
+ 0,
153
+ quoteAta,
154
+ true
155
+ );
156
+
157
+ const tx = await client.buildTransaction(ixs);
158
+ const { txSig } = await client.sendTransaction(tx);
159
+ console.log(
160
+ `Withdrew isolated deposit for perp market ${pos.marketIndex}: ${txSig}`
161
+ );
162
+ } catch (e) {
163
+ console.error(`Failed to withdraw for market ${pos.marketIndex}:`, e);
164
+ }
165
+ }
166
+
167
+ await user.unsubscribe();
168
+ await client.unsubscribe();
169
+ }
170
+
171
+ main().catch((e) => {
172
+ console.error(e);
173
+ process.exit(1);
174
+ });
@@ -1428,6 +1428,17 @@ export const MainnetPerpMarkets: PerpMarketConfig[] = [
1428
1428
  oracleSource: OracleSource.PYTH_LAZER,
1429
1429
  pythLazerId: 2382,
1430
1430
  },
1431
+ {
1432
+ fullName: 'Monad',
1433
+ category: ['L1'],
1434
+ symbol: '1KMON-PERP',
1435
+ baseAssetSymbol: '1KMON',
1436
+ marketIndex: 83,
1437
+ oracle: new PublicKey('585jsthKg9BeTfnFGAxgfNie9krGGyPbd5feMpWneHf7'),
1438
+ launchTs: 1763996757000,
1439
+ oracleSource: OracleSource.PYTH_LAZER_1K,
1440
+ pythLazerId: 2396,
1441
+ },
1431
1442
  ];
1432
1443
 
1433
1444
  export const PerpMarkets: { [key in DriftEnv]: PerpMarketConfig[] } = {
@@ -993,6 +993,20 @@ export const MainnetSpotMarkets: SpotMarketConfig[] = [
993
993
  pythLazerId: 2382,
994
994
  launchTs: 1761225524000,
995
995
  },
996
+ {
997
+ symbol: 'CASH',
998
+ marketIndex: 61,
999
+ poolId: 0,
1000
+ oracle: new PublicKey('AK6coxSjfAnuDT4ZUSP3UpeQe2G1tKcALnsdd835eg7T'),
1001
+ oracleSource: OracleSource.PYTH_LAZER_STABLE_COIN,
1002
+ mint: new PublicKey('CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH'),
1003
+ precision: new BN(10).pow(SIX),
1004
+ precisionExp: SIX,
1005
+ pythFeedId:
1006
+ '0xdf3320ef0f4617337b8dbb924f2aaa4f9db08f522a5435b44f9066c1ac4c7f95',
1007
+ pythLazerId: 2323,
1008
+ launchTs: 1763677264000,
1009
+ },
996
1010
  ];
997
1011
 
998
1012
  export const SpotMarkets: { [key in DriftEnv]: SpotMarketConfig[] } = {
@@ -84,7 +84,7 @@ export function decodeUser(buffer: Buffer): UserAccount {
84
84
  const quoteAssetAmount = readSignedBigInt64LE(buffer, offset + 16);
85
85
  const lpShares = readUnsignedBigInt64LE(buffer, offset + 64);
86
86
  const openOrders = buffer.readUInt8(offset + 94);
87
- const positionFlag = buffer.readUInt8(offset + 95);
87
+ const positionFlag = 0;
88
88
  const isolatedPositionScaledBalance = readUnsignedBigInt64LE(
89
89
  buffer,
90
90
  offset + 96
@@ -122,7 +122,6 @@ export function decodeUser(buffer: Buffer): UserAccount {
122
122
  offset += 3;
123
123
  const perLpBase = buffer.readUInt8(offset);
124
124
  offset += 1;
125
-
126
125
  perpPositions.push({
127
126
  lastCumulativeFundingRate,
128
127
  baseAssetAmount,
@@ -140,8 +139,8 @@ export function decodeUser(buffer: Buffer): UserAccount {
140
139
  openOrders,
141
140
  perLpBase,
142
141
  maxMarginRatio,
143
- isolatedPositionScaledBalance,
144
142
  positionFlag,
143
+ isolatedPositionScaledBalance,
145
144
  });
146
145
  }
147
146