@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.
- package/.env +4 -0
- package/VERSION +1 -1
- package/lib/browser/constants/perpMarkets.js +11 -0
- package/lib/browser/constants/spotMarkets.js +13 -0
- package/lib/browser/decode/user.js +2 -2
- package/lib/browser/driftClient.d.ts +20 -8
- package/lib/browser/driftClient.js +216 -17
- package/lib/browser/idl/drift.json +225 -21
- package/lib/browser/math/margin.js +2 -1
- package/lib/browser/math/position.d.ts +1 -0
- package/lib/browser/math/position.js +10 -2
- package/lib/browser/math/superStake.d.ts +3 -2
- package/lib/browser/types.d.ts +12 -6
- package/lib/browser/types.js +11 -6
- package/lib/browser/user.d.ts +3 -2
- package/lib/browser/user.js +24 -8
- package/lib/node/constants/perpMarkets.d.ts.map +1 -1
- package/lib/node/constants/perpMarkets.js +11 -0
- package/lib/node/constants/spotMarkets.d.ts.map +1 -1
- package/lib/node/constants/spotMarkets.js +13 -0
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +2 -2
- package/lib/node/driftClient.d.ts +20 -8
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +216 -17
- package/lib/node/idl/drift.json +225 -21
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +2 -1
- package/lib/node/math/position.d.ts +1 -0
- package/lib/node/math/position.d.ts.map +1 -1
- package/lib/node/math/position.js +10 -2
- package/lib/node/math/spotBalance.d.ts.map +1 -1
- package/lib/node/math/superStake.d.ts +3 -2
- package/lib/node/math/superStake.d.ts.map +1 -1
- package/lib/node/types.d.ts +12 -6
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +11 -6
- package/lib/node/user.d.ts +3 -2
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +24 -8
- package/package.json +1 -1
- package/scripts/deposit-isolated-positions.ts +110 -0
- package/scripts/single-grpc-client-test.ts +71 -21
- package/scripts/withdraw-isolated-positions.ts +174 -0
- package/src/constants/perpMarkets.ts +11 -0
- package/src/constants/spotMarkets.ts +14 -0
- package/src/decode/user.ts +2 -3
- package/src/driftClient.ts +464 -41
- package/src/idl/drift.json +226 -22
- package/src/margin/README.md +143 -0
- package/src/math/margin.ts +3 -4
- package/src/math/position.ts +12 -2
- package/src/math/spotBalance.ts +0 -1
- package/src/types.ts +15 -7
- package/src/user.ts +49 -15
- package/tests/amm/test.ts +1 -1
- package/tests/dlob/helpers.ts +1 -1
- 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
|
-
[
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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(
|
|
196
|
-
console.log(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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(
|
|
215
|
-
|
|
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[] } = {
|
package/src/decode/user.ts
CHANGED
|
@@ -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 =
|
|
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
|
|