@drift-labs/sdk 2.144.0-beta.5 → 2.145.0-beta.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/VERSION +1 -1
- package/lib/browser/accounts/grpcMultiAccountSubscriber.js +1 -0
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.d.ts +25 -0
- package/lib/browser/accounts/grpcMultiUserAccountSubscriber.js +196 -0
- package/lib/browser/driftClient.js +26 -25
- package/lib/browser/driftClientConfig.d.ts +2 -0
- package/lib/browser/idl/drift.json +1 -1
- package/lib/browser/user.js +10 -4
- package/lib/browser/userConfig.d.ts +2 -0
- package/lib/node/accounts/grpcMultiAccountSubscriber.d.ts.map +1 -1
- package/lib/node/accounts/grpcMultiAccountSubscriber.js +1 -0
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts +26 -0
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.d.ts.map +1 -0
- package/lib/node/accounts/grpcMultiUserAccountSubscriber.js +196 -0
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +26 -25
- package/lib/node/driftClientConfig.d.ts +2 -0
- package/lib/node/driftClientConfig.d.ts.map +1 -1
- package/lib/node/idl/drift.json +1 -1
- package/lib/node/user.d.ts.map +1 -1
- package/lib/node/user.js +10 -4
- package/lib/node/userConfig.d.ts +2 -0
- package/lib/node/userConfig.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/grpc-multiuser-client-test-comparison.ts +156 -0
- package/scripts/single-grpc-client-test.ts +20 -10
- package/src/accounts/grpcMultiAccountSubscriber.ts +1 -0
- package/src/accounts/grpcMultiUserAccountSubscriber.ts +274 -0
- package/src/driftClient.ts +2 -0
- package/src/driftClientConfig.ts +2 -0
- package/src/idl/drift.json +1 -1
- package/src/user.ts +16 -9
- package/src/userConfig.ts +2 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { grpcUserAccountSubscriber } from '../src/accounts/grpcUserAccountSubscriber';
|
|
2
|
+
import { grpcMultiUserAccountSubscriber } from '../src/accounts/grpcMultiUserAccountSubscriber';
|
|
3
|
+
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
4
|
+
import { DRIFT_PROGRAM_ID } from '../src';
|
|
5
|
+
import { CommitmentLevel } from '@triton-one/yellowstone-grpc';
|
|
6
|
+
import { AnchorProvider, Idl, Program } from '@coral-xyz/anchor';
|
|
7
|
+
import driftIDL from '../src/idl/drift.json';
|
|
8
|
+
import assert from 'assert';
|
|
9
|
+
import { Wallet } from '../src';
|
|
10
|
+
|
|
11
|
+
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
|
|
12
|
+
const TOKEN = process.env.TOKEN;
|
|
13
|
+
const RPC_ENDPOINT = process.env.RPC_ENDPOINT;
|
|
14
|
+
|
|
15
|
+
const USER_ACCOUNT_PUBKEYS = [
|
|
16
|
+
// Add user account public keys here, e.g.:
|
|
17
|
+
// new PublicKey('...')
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
async function testGrpcUserAccountSubscriberV1VsV2() {
|
|
21
|
+
console.log('🚀 Initializing User Account Subscriber V1 vs V2 Test...');
|
|
22
|
+
|
|
23
|
+
if (USER_ACCOUNT_PUBKEYS.length === 0) {
|
|
24
|
+
console.error('❌ No user account public keys provided. Please add some to USER_ACCOUNT_PUBKEYS array.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const connection = new Connection(RPC_ENDPOINT);
|
|
29
|
+
const wallet = new Wallet(new Keypair());
|
|
30
|
+
|
|
31
|
+
const programId = new PublicKey(DRIFT_PROGRAM_ID);
|
|
32
|
+
const provider = new AnchorProvider(
|
|
33
|
+
connection,
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
wallet,
|
|
36
|
+
{
|
|
37
|
+
commitment: 'processed',
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const program = new Program(driftIDL as Idl, programId, provider);
|
|
42
|
+
|
|
43
|
+
const grpcConfigs = {
|
|
44
|
+
endpoint: GRPC_ENDPOINT,
|
|
45
|
+
token: TOKEN,
|
|
46
|
+
commitmentLevel: CommitmentLevel.PROCESSED,
|
|
47
|
+
channelOptions: {
|
|
48
|
+
'grpc.keepalive_time_ms': 10_000,
|
|
49
|
+
'grpc.keepalive_timeout_ms': 1_000,
|
|
50
|
+
'grpc.keepalive_permit_without_calls': 1,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
console.log(`📊 Testing ${USER_ACCOUNT_PUBKEYS.length} user accounts...`);
|
|
55
|
+
|
|
56
|
+
// V1: Create individual subscribers for each user account
|
|
57
|
+
const v1Subscribers = USER_ACCOUNT_PUBKEYS.map(
|
|
58
|
+
(pubkey) =>
|
|
59
|
+
new grpcUserAccountSubscriber(
|
|
60
|
+
grpcConfigs,
|
|
61
|
+
program,
|
|
62
|
+
pubkey,
|
|
63
|
+
{ logResubMessages: true }
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// V2: Create a single multi-subscriber and get per-user interfaces
|
|
68
|
+
const v2MultiSubscriber = new grpcMultiUserAccountSubscriber(
|
|
69
|
+
program,
|
|
70
|
+
grpcConfigs,
|
|
71
|
+
{ logResubMessages: true }
|
|
72
|
+
);
|
|
73
|
+
const v2Subscribers = USER_ACCOUNT_PUBKEYS.map((pubkey) =>
|
|
74
|
+
v2MultiSubscriber.forUser(pubkey)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Subscribe all V1 subscribers
|
|
78
|
+
console.log('🔗 Subscribing V1 subscribers...');
|
|
79
|
+
await Promise.all(v1Subscribers.map((sub) => sub.subscribe()));
|
|
80
|
+
console.log('✅ V1 subscribers ready');
|
|
81
|
+
|
|
82
|
+
// Subscribe all V2 subscribers
|
|
83
|
+
console.log('🔗 Subscribing V2 subscribers...');
|
|
84
|
+
await v2MultiSubscriber.subscribe();
|
|
85
|
+
console.log('✅ V2 subscribers ready');
|
|
86
|
+
|
|
87
|
+
const compare = () => {
|
|
88
|
+
try {
|
|
89
|
+
let passedTests = 0;
|
|
90
|
+
let totalTests = 0;
|
|
91
|
+
|
|
92
|
+
// Test each user account
|
|
93
|
+
for (let i = 0; i < USER_ACCOUNT_PUBKEYS.length; i++) {
|
|
94
|
+
const pubkey = USER_ACCOUNT_PUBKEYS[i];
|
|
95
|
+
const v1Sub = v1Subscribers[i];
|
|
96
|
+
const v2Sub = v2Subscribers[i];
|
|
97
|
+
|
|
98
|
+
totalTests++;
|
|
99
|
+
|
|
100
|
+
// 1. Test isSubscribed
|
|
101
|
+
assert.strictEqual(
|
|
102
|
+
v1Sub.isSubscribed,
|
|
103
|
+
v2Sub.isSubscribed,
|
|
104
|
+
`User ${pubkey.toBase58()}: isSubscribed should match`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// 2. Test getUserAccountAndSlot
|
|
108
|
+
const v1Data = v1Sub.getUserAccountAndSlot();
|
|
109
|
+
const v2Data = v2Sub.getUserAccountAndSlot();
|
|
110
|
+
|
|
111
|
+
// Compare the user account data
|
|
112
|
+
assert.deepStrictEqual(
|
|
113
|
+
v1Data.data,
|
|
114
|
+
v2Data.data,
|
|
115
|
+
`User ${pubkey.toBase58()}: account data should match`
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Slots might differ slightly due to timing, but let's check if they're close
|
|
119
|
+
const slotDiff = Math.abs(v1Data.slot - v2Data.slot);
|
|
120
|
+
if (slotDiff > 10) {
|
|
121
|
+
console.warn(
|
|
122
|
+
`⚠️ User ${pubkey.toBase58()}: slot difference is ${slotDiff} (v1: ${v1Data.slot}, v2: ${v2Data.slot})`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
passedTests++;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log(`✅ All comparisons passed (${passedTests}/${totalTests} user accounts)`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('❌ Comparison failed:', error);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Run initial comparison
|
|
136
|
+
compare();
|
|
137
|
+
|
|
138
|
+
// Run comparison every second to verify live updates
|
|
139
|
+
const interval = setInterval(compare, 1000);
|
|
140
|
+
|
|
141
|
+
const cleanup = async () => {
|
|
142
|
+
clearInterval(interval);
|
|
143
|
+
console.log('🧹 Cleaning up...');
|
|
144
|
+
await Promise.all([
|
|
145
|
+
...v1Subscribers.map((sub) => sub.unsubscribe()),
|
|
146
|
+
...v2Subscribers.map((sub) => sub.unsubscribe()),
|
|
147
|
+
]);
|
|
148
|
+
console.log('✅ Cleanup complete');
|
|
149
|
+
process.exit(0);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
process.on('SIGINT', cleanup);
|
|
153
|
+
process.on('SIGTERM', cleanup);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
testGrpcUserAccountSubscriberV1VsV2().catch(console.error);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
ProgramAccount,
|
|
20
20
|
} from '@coral-xyz/anchor';
|
|
21
21
|
import driftIDL from '../src/idl/drift.json';
|
|
22
|
+
import { grpcMultiUserAccountSubscriber } from '../src/accounts/grpcMultiUserAccountSubscriber';
|
|
22
23
|
|
|
23
24
|
const GRPC_ENDPOINT = process.env.GRPC_ENDPOINT;
|
|
24
25
|
const TOKEN = process.env.TOKEN;
|
|
@@ -96,18 +97,27 @@ async function initializeSingleGrpcClient() {
|
|
|
96
97
|
console.log(`📊 Markets: ${perpMarketIndexes.length} perp, ${spotMarketIndexes.length} spot`);
|
|
97
98
|
console.log(`🔮 Oracles: ${oracleInfos.length}`);
|
|
98
99
|
|
|
100
|
+
|
|
101
|
+
const grpcConfigs = {
|
|
102
|
+
endpoint: GRPC_ENDPOINT,
|
|
103
|
+
token: TOKEN,
|
|
104
|
+
commitmentLevel: CommitmentLevel.PROCESSED,
|
|
105
|
+
channelOptions: {
|
|
106
|
+
'grpc.keepalive_time_ms': 10_000,
|
|
107
|
+
'grpc.keepalive_timeout_ms': 1_000,
|
|
108
|
+
'grpc.keepalive_permit_without_calls': 1,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const multiUserSubsciber = new grpcMultiUserAccountSubscriber(
|
|
113
|
+
program,
|
|
114
|
+
grpcConfigs
|
|
115
|
+
);
|
|
116
|
+
|
|
99
117
|
const baseAccountSubscription = {
|
|
100
118
|
type: 'grpc' as const,
|
|
101
|
-
grpcConfigs
|
|
102
|
-
|
|
103
|
-
token: TOKEN,
|
|
104
|
-
commitmentLevel: CommitmentLevel.PROCESSED,
|
|
105
|
-
channelOptions: {
|
|
106
|
-
'grpc.keepalive_time_ms': 10_000,
|
|
107
|
-
'grpc.keepalive_timeout_ms': 1_000,
|
|
108
|
-
'grpc.keepalive_permit_without_calls': 1,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
119
|
+
grpcConfigs,
|
|
120
|
+
grpcMultiUserAccountSubscriber: multiUserSubsciber,
|
|
111
121
|
};
|
|
112
122
|
|
|
113
123
|
const config: DriftClientConfig = {
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DataAndSlot,
|
|
3
|
+
GrpcConfigs,
|
|
4
|
+
NotSubscribedError,
|
|
5
|
+
ResubOpts,
|
|
6
|
+
UserAccountEvents,
|
|
7
|
+
UserAccountSubscriber,
|
|
8
|
+
} from './types';
|
|
9
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import { Context, PublicKey } from '@solana/web3.js';
|
|
12
|
+
import { Program } from '@coral-xyz/anchor';
|
|
13
|
+
import { UserAccount } from '../types';
|
|
14
|
+
import { grpcMultiAccountSubscriber } from './grpcMultiAccountSubscriber';
|
|
15
|
+
|
|
16
|
+
export class grpcMultiUserAccountSubscriber {
|
|
17
|
+
private program: Program;
|
|
18
|
+
private multiSubscriber: grpcMultiAccountSubscriber<UserAccount>;
|
|
19
|
+
|
|
20
|
+
private userData = new Map<string, DataAndSlot<UserAccount>>();
|
|
21
|
+
private listeners = new Map<
|
|
22
|
+
string,
|
|
23
|
+
Set<StrictEventEmitter<EventEmitter, UserAccountEvents>>
|
|
24
|
+
>();
|
|
25
|
+
private keyToPk = new Map<string, PublicKey>();
|
|
26
|
+
private pendingAddKeys = new Set<string>();
|
|
27
|
+
private debounceTimer?: ReturnType<typeof setTimeout>;
|
|
28
|
+
private debounceMs = 20;
|
|
29
|
+
private isMultiSubscribed = false;
|
|
30
|
+
private userAccountSubscribers = new Map<string, UserAccountSubscriber>();
|
|
31
|
+
private grpcConfigs: GrpcConfigs;
|
|
32
|
+
resubOpts?: ResubOpts;
|
|
33
|
+
|
|
34
|
+
private handleAccountChange = (
|
|
35
|
+
accountId: PublicKey,
|
|
36
|
+
data: UserAccount,
|
|
37
|
+
context: Context,
|
|
38
|
+
_buffer?: unknown,
|
|
39
|
+
_accountProps?: unknown
|
|
40
|
+
): void => {
|
|
41
|
+
const k = accountId.toBase58();
|
|
42
|
+
this.userData.set(k, { data, slot: context.slot });
|
|
43
|
+
const setForKey = this.listeners.get(k);
|
|
44
|
+
if (setForKey) {
|
|
45
|
+
for (const emitter of setForKey) {
|
|
46
|
+
emitter.emit('userAccountUpdate', data);
|
|
47
|
+
emitter.emit('update');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
public constructor(
|
|
53
|
+
program: Program,
|
|
54
|
+
grpcConfigs: GrpcConfigs,
|
|
55
|
+
resubOpts?: ResubOpts,
|
|
56
|
+
multiSubscriber?: grpcMultiAccountSubscriber<UserAccount>
|
|
57
|
+
) {
|
|
58
|
+
this.program = program;
|
|
59
|
+
this.multiSubscriber = multiSubscriber;
|
|
60
|
+
this.grpcConfigs = grpcConfigs;
|
|
61
|
+
this.resubOpts = resubOpts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public async subscribe(): Promise<void> {
|
|
65
|
+
if (!this.multiSubscriber) {
|
|
66
|
+
this.multiSubscriber =
|
|
67
|
+
await grpcMultiAccountSubscriber.create<UserAccount>(
|
|
68
|
+
this.grpcConfigs,
|
|
69
|
+
'user',
|
|
70
|
+
this.program,
|
|
71
|
+
undefined,
|
|
72
|
+
this.resubOpts
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Subscribe all per-user subscribers first
|
|
77
|
+
await Promise.all(
|
|
78
|
+
Array.from(this.userAccountSubscribers.values()).map((subscriber) =>
|
|
79
|
+
subscriber.subscribe()
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
// Ensure we immediately register any pending keys and kick off underlying subscription/fetch
|
|
83
|
+
await this.flushPending();
|
|
84
|
+
// Proactively fetch once to populate data for all subscribed accounts
|
|
85
|
+
await this.multiSubscriber.fetch();
|
|
86
|
+
// Wait until the underlying multi-subscriber has data for every registered user key
|
|
87
|
+
const targetKeys = Array.from(this.listeners.keys());
|
|
88
|
+
if (targetKeys.length === 0) return;
|
|
89
|
+
// Poll until all keys are present in dataMap
|
|
90
|
+
// Use debounceMs as the polling cadence to avoid introducing new magic numbers
|
|
91
|
+
// eslint-disable-next-line no-constant-condition
|
|
92
|
+
while (true) {
|
|
93
|
+
const map = this.multiSubscriber.getAccountDataMap();
|
|
94
|
+
let allPresent = true;
|
|
95
|
+
for (const k of targetKeys) {
|
|
96
|
+
if (!map.has(k)) {
|
|
97
|
+
allPresent = false;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (allPresent) break;
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, this.debounceMs));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public forUser(userAccountPublicKey: PublicKey): UserAccountSubscriber {
|
|
107
|
+
if (this.userAccountSubscribers.has(userAccountPublicKey.toBase58())) {
|
|
108
|
+
return this.userAccountSubscribers.get(userAccountPublicKey.toBase58())!;
|
|
109
|
+
}
|
|
110
|
+
const key = userAccountPublicKey.toBase58();
|
|
111
|
+
const perUserEmitter: StrictEventEmitter<EventEmitter, UserAccountEvents> =
|
|
112
|
+
new EventEmitter();
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
114
|
+
const parent = this;
|
|
115
|
+
let isSubscribed = false;
|
|
116
|
+
|
|
117
|
+
const registerHandlerIfNeeded = async () => {
|
|
118
|
+
if (!this.listeners.has(key)) {
|
|
119
|
+
this.listeners.set(key, new Set());
|
|
120
|
+
this.keyToPk.set(key, userAccountPublicKey);
|
|
121
|
+
this.pendingAddKeys.add(key);
|
|
122
|
+
this.scheduleFlush();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const perUser: UserAccountSubscriber = {
|
|
127
|
+
get eventEmitter() {
|
|
128
|
+
return perUserEmitter;
|
|
129
|
+
},
|
|
130
|
+
set eventEmitter(_v) {},
|
|
131
|
+
|
|
132
|
+
get isSubscribed() {
|
|
133
|
+
return isSubscribed;
|
|
134
|
+
},
|
|
135
|
+
set isSubscribed(_v: boolean) {
|
|
136
|
+
isSubscribed = _v;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async subscribe(userAccount?: UserAccount): Promise<boolean> {
|
|
140
|
+
if (isSubscribed) return true;
|
|
141
|
+
if (userAccount) {
|
|
142
|
+
this.updateData(userAccount, 0);
|
|
143
|
+
}
|
|
144
|
+
await registerHandlerIfNeeded();
|
|
145
|
+
const setForKey = parent.listeners.get(key)!;
|
|
146
|
+
setForKey.add(perUserEmitter);
|
|
147
|
+
isSubscribed = true;
|
|
148
|
+
return true;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
async fetch(): Promise<void> {
|
|
152
|
+
if (!isSubscribed) {
|
|
153
|
+
throw new NotSubscribedError(
|
|
154
|
+
'Must subscribe before fetching account updates'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const account = (await parent.program.account.user.fetch(
|
|
158
|
+
userAccountPublicKey
|
|
159
|
+
)) as UserAccount;
|
|
160
|
+
this.updateData(account, 0);
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
updateData(userAccount: UserAccount, slot: number): void {
|
|
164
|
+
parent.userData.set(key, { data: userAccount, slot });
|
|
165
|
+
perUserEmitter.emit('userAccountUpdate', userAccount);
|
|
166
|
+
perUserEmitter.emit('update');
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
async unsubscribe(): Promise<void> {
|
|
170
|
+
if (!isSubscribed) return;
|
|
171
|
+
const setForKey = parent.listeners.get(key);
|
|
172
|
+
if (setForKey) {
|
|
173
|
+
setForKey.delete(perUserEmitter);
|
|
174
|
+
if (setForKey.size === 0) {
|
|
175
|
+
parent.listeners.delete(key);
|
|
176
|
+
await parent.multiSubscriber.removeAccounts([userAccountPublicKey]);
|
|
177
|
+
parent.userData.delete(key);
|
|
178
|
+
parent.keyToPk.delete(key);
|
|
179
|
+
parent.pendingAddKeys.delete(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
isSubscribed = false;
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
getUserAccountAndSlot(): DataAndSlot<UserAccount> {
|
|
186
|
+
const das = parent.userData.get(key);
|
|
187
|
+
if (!das) {
|
|
188
|
+
throw new NotSubscribedError(
|
|
189
|
+
'Must subscribe before getting user account data'
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return das;
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
this.userAccountSubscribers.set(userAccountPublicKey.toBase58(), perUser);
|
|
197
|
+
return perUser;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private scheduleFlush(): void {
|
|
201
|
+
if (this.debounceTimer) return;
|
|
202
|
+
this.debounceTimer = setTimeout(() => {
|
|
203
|
+
void this.flushPending();
|
|
204
|
+
}, this.debounceMs);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async flushPending(): Promise<void> {
|
|
208
|
+
const hasPending = this.pendingAddKeys.size > 0;
|
|
209
|
+
if (!hasPending) {
|
|
210
|
+
this.debounceTimer = undefined;
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const allPks: PublicKey[] = [];
|
|
215
|
+
for (const k of this.listeners.keys()) {
|
|
216
|
+
const pk = this.keyToPk.get(k);
|
|
217
|
+
if (pk) allPks.push(pk);
|
|
218
|
+
}
|
|
219
|
+
if (allPks.length === 0) {
|
|
220
|
+
this.pendingAddKeys.clear();
|
|
221
|
+
this.debounceTimer = undefined;
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!this.isMultiSubscribed) {
|
|
226
|
+
await this.multiSubscriber.subscribe(allPks, this.handleAccountChange);
|
|
227
|
+
this.isMultiSubscribed = true;
|
|
228
|
+
await this.multiSubscriber.fetch();
|
|
229
|
+
for (const k of this.pendingAddKeys) {
|
|
230
|
+
const pk = this.keyToPk.get(k);
|
|
231
|
+
if (pk) {
|
|
232
|
+
const data = this.multiSubscriber.getAccountData(k);
|
|
233
|
+
if (data) {
|
|
234
|
+
this.handleAccountChange(
|
|
235
|
+
pk,
|
|
236
|
+
data.data,
|
|
237
|
+
{ slot: data.slot },
|
|
238
|
+
undefined,
|
|
239
|
+
undefined
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
const ms = this.multiSubscriber as unknown as {
|
|
246
|
+
onChangeMap: Map<
|
|
247
|
+
string,
|
|
248
|
+
(
|
|
249
|
+
data: UserAccount,
|
|
250
|
+
context: Context,
|
|
251
|
+
buffer: unknown,
|
|
252
|
+
accountProps: unknown
|
|
253
|
+
) => void
|
|
254
|
+
>;
|
|
255
|
+
};
|
|
256
|
+
for (const k of this.pendingAddKeys) {
|
|
257
|
+
ms.onChangeMap.set(k, (data, ctx, buffer, accountProps) => {
|
|
258
|
+
this.multiSubscriber.setAccountData(k, data, ctx.slot);
|
|
259
|
+
this.handleAccountChange(
|
|
260
|
+
new PublicKey(k),
|
|
261
|
+
data,
|
|
262
|
+
ctx,
|
|
263
|
+
buffer,
|
|
264
|
+
accountProps
|
|
265
|
+
);
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
await this.multiSubscriber.addAccounts(allPks);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
this.pendingAddKeys.clear();
|
|
272
|
+
this.debounceTimer = undefined;
|
|
273
|
+
}
|
|
274
|
+
}
|
package/src/driftClient.ts
CHANGED
|
@@ -379,6 +379,8 @@ export class DriftClient {
|
|
|
379
379
|
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
|
|
380
380
|
logResubMessages: config.accountSubscription?.logResubMessages,
|
|
381
381
|
grpcConfigs: config.accountSubscription?.grpcConfigs,
|
|
382
|
+
grpcMultiUserAccountSubscriber:
|
|
383
|
+
config.accountSubscription?.grpcMultiUserAccountSubscriber,
|
|
382
384
|
};
|
|
383
385
|
this.userStatsAccountSubscriptionConfig = {
|
|
384
386
|
type: 'grpc',
|
package/src/driftClientConfig.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { WebSocketDriftClientAccountSubscriberV2 } from './accounts/webSocketDri
|
|
|
24
24
|
import { WebSocketDriftClientAccountSubscriber } from './accounts/webSocketDriftClientAccountSubscriber';
|
|
25
25
|
import { grpcDriftClientAccountSubscriberV2 } from './accounts/grpcDriftClientAccountSubscriberV2';
|
|
26
26
|
import { grpcDriftClientAccountSubscriber } from './accounts/grpcDriftClientAccountSubscriber';
|
|
27
|
+
import { grpcMultiUserAccountSubscriber } from './accounts/grpcMultiUserAccountSubscriber';
|
|
27
28
|
|
|
28
29
|
export type DriftClientConfig = {
|
|
29
30
|
connection: Connection;
|
|
@@ -73,6 +74,7 @@ export type DriftClientSubscriptionConfig =
|
|
|
73
74
|
) =>
|
|
74
75
|
| grpcDriftClientAccountSubscriberV2
|
|
75
76
|
| grpcDriftClientAccountSubscriber;
|
|
77
|
+
grpcMultiUserAccountSubscriber?: grpcMultiUserAccountSubscriber;
|
|
76
78
|
}
|
|
77
79
|
| {
|
|
78
80
|
type: 'websocket';
|
package/src/idl/drift.json
CHANGED
package/src/user.ts
CHANGED
|
@@ -137,15 +137,22 @@ export class User {
|
|
|
137
137
|
} else if (config.accountSubscription?.type === 'custom') {
|
|
138
138
|
this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
|
|
139
139
|
} else if (config.accountSubscription?.type === 'grpc') {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
140
|
+
if (config.accountSubscription.grpcMultiUserAccountSubscriber) {
|
|
141
|
+
this.accountSubscriber =
|
|
142
|
+
config.accountSubscription.grpcMultiUserAccountSubscriber.forUser(
|
|
143
|
+
config.userAccountPublicKey
|
|
144
|
+
);
|
|
145
|
+
} else {
|
|
146
|
+
this.accountSubscriber = new grpcUserAccountSubscriber(
|
|
147
|
+
config.accountSubscription.grpcConfigs,
|
|
148
|
+
config.driftClient.program,
|
|
149
|
+
config.userAccountPublicKey,
|
|
150
|
+
{
|
|
151
|
+
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
|
|
152
|
+
logResubMessages: config.accountSubscription?.logResubMessages,
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
149
156
|
} else {
|
|
150
157
|
if (
|
|
151
158
|
config.accountSubscription?.type === 'websocket' &&
|
package/src/userConfig.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { BulkAccountLoader } from './accounts/bulkAccountLoader';
|
|
|
4
4
|
import { GrpcConfigs, UserAccountSubscriber } from './accounts/types';
|
|
5
5
|
import { WebSocketProgramAccountSubscriber } from './accounts/webSocketProgramAccountSubscriber';
|
|
6
6
|
import { UserAccount } from './types';
|
|
7
|
+
import { grpcMultiUserAccountSubscriber } from './accounts/grpcMultiUserAccountSubscriber';
|
|
7
8
|
|
|
8
9
|
export type UserConfig = {
|
|
9
10
|
accountSubscription?: UserSubscriptionConfig;
|
|
@@ -17,6 +18,7 @@ export type UserSubscriptionConfig =
|
|
|
17
18
|
resubTimeoutMs?: number;
|
|
18
19
|
logResubMessages?: boolean;
|
|
19
20
|
grpcConfigs: GrpcConfigs;
|
|
21
|
+
grpcMultiUserAccountSubscriber?: grpcMultiUserAccountSubscriber;
|
|
20
22
|
}
|
|
21
23
|
| {
|
|
22
24
|
type: 'websocket';
|