@drift-labs/sdk 2.145.0 → 2.146.0-alpha.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/.env +4 -0
- package/VERSION +1 -1
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/browser/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/browser/adminClient.d.ts +5 -1
- package/lib/browser/adminClient.js +57 -23
- package/lib/browser/constants/numericConstants.d.ts +2 -0
- package/lib/browser/constants/numericConstants.js +5 -1
- package/lib/browser/constants/perpMarkets.js +0 -2
- package/lib/browser/decode/user.js +4 -0
- package/lib/browser/driftClient.d.ts +25 -10
- package/lib/browser/driftClient.js +238 -41
- package/lib/browser/driftClientConfig.d.ts +7 -2
- package/lib/browser/idl/drift.json +245 -22
- package/lib/browser/index.d.ts +4 -0
- package/lib/browser/index.js +9 -1
- package/lib/browser/marginCalculation.d.ts +86 -0
- package/lib/browser/marginCalculation.js +209 -0
- package/lib/browser/math/margin.d.ts +1 -1
- package/lib/browser/math/margin.js +8 -1
- package/lib/browser/math/position.d.ts +1 -0
- package/lib/browser/math/position.js +10 -2
- package/lib/browser/math/spotPosition.d.ts +1 -1
- package/lib/browser/math/spotPosition.js +3 -2
- package/lib/browser/math/superStake.d.ts +3 -2
- package/lib/browser/types.d.ts +13 -0
- package/lib/browser/types.js +12 -1
- package/lib/browser/user.d.ts +59 -11
- package/lib/browser/user.js +348 -43
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +8 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts +99 -7
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.d.ts.map +1 -1
- package/lib/node/accounts/webSocketProgramAccountSubscriberV2.js +435 -144
- package/lib/node/adminClient.d.ts +5 -1
- package/lib/node/adminClient.d.ts.map +1 -1
- package/lib/node/adminClient.js +57 -23
- package/lib/node/constants/numericConstants.d.ts +2 -0
- package/lib/node/constants/numericConstants.d.ts.map +1 -1
- package/lib/node/constants/numericConstants.js +5 -1
- package/lib/node/constants/perpMarkets.d.ts.map +1 -1
- package/lib/node/constants/perpMarkets.js +0 -2
- package/lib/node/decode/user.d.ts.map +1 -1
- package/lib/node/decode/user.js +4 -0
- package/lib/node/driftClient.d.ts +25 -10
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +238 -41
- package/lib/node/driftClientConfig.d.ts +7 -2
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +245 -22
- package/lib/node/index.d.ts +4 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +9 -1
- package/lib/node/marginCalculation.d.ts +87 -0
- package/lib/node/marginCalculation.d.ts.map +1 -0
- package/lib/node/marginCalculation.js +209 -0
- package/lib/node/math/margin.d.ts +1 -1
- package/lib/node/math/margin.d.ts.map +1 -1
- package/lib/node/math/margin.js +8 -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/spotPosition.d.ts +1 -1
- package/lib/node/math/spotPosition.d.ts.map +1 -1
- package/lib/node/math/spotPosition.js +3 -2
- 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 +13 -0
- package/lib/node/types.d.ts.map +1 -1
- package/lib/node/types.js +12 -1
- package/lib/node/user.d.ts +59 -11
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +348 -43
- 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/accounts/grpcMultiUserAccountSubscriber.ts +8 -1
- package/src/accounts/webSocketProgramAccountSubscriberV2.ts +566 -167
- package/src/adminClient.ts +74 -25
- package/src/constants/numericConstants.ts +5 -0
- package/src/constants/perpMarkets.ts +0 -3
- package/src/decode/user.ts +7 -1
- package/src/driftClient.ts +465 -52
- package/src/driftClientConfig.ts +15 -8
- package/src/idl/drift.json +246 -23
- package/src/index.ts +4 -0
- package/src/margin/README.md +143 -0
- package/src/marginCalculation.ts +306 -0
- package/src/math/margin.ts +13 -1
- package/src/math/position.ts +12 -2
- package/src/math/spotPosition.ts +6 -2
- package/src/types.ts +16 -0
- package/src/user.ts +623 -81
- package/tests/amm/test.ts +1 -1
- package/tests/dlob/helpers.ts +6 -3
- package/tests/user/getMarginCalculation.ts +405 -0
- 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
|
+
});
|
|
@@ -119,7 +119,10 @@ export class grpcMultiUserAccountSubscriber {
|
|
|
119
119
|
this.listeners.set(key, new Set());
|
|
120
120
|
this.keyToPk.set(key, userAccountPublicKey);
|
|
121
121
|
this.pendingAddKeys.add(key);
|
|
122
|
-
this.
|
|
122
|
+
if (this.isMultiSubscribed) {
|
|
123
|
+
// only schedule flush if already subscribed to the multi-subscriber
|
|
124
|
+
this.scheduleFlush();
|
|
125
|
+
}
|
|
123
126
|
}
|
|
124
127
|
};
|
|
125
128
|
|
|
@@ -161,6 +164,10 @@ export class grpcMultiUserAccountSubscriber {
|
|
|
161
164
|
},
|
|
162
165
|
|
|
163
166
|
updateData(userAccount: UserAccount, slot: number): void {
|
|
167
|
+
const existingData = parent.userData.get(key);
|
|
168
|
+
if (existingData && existingData.slot > slot) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
164
171
|
parent.userData.set(key, { data: userAccount, slot });
|
|
165
172
|
perUserEmitter.emit('userAccountUpdate', userAccount);
|
|
166
173
|
perUserEmitter.emit('update');
|