-
-
Notifications
You must be signed in to change notification settings - Fork 288
Expand file tree
/
Copy pathTradingReadinessCache.ts
More file actions
364 lines (331 loc) · 10.9 KB
/
Copy pathTradingReadinessCache.ts
File metadata and controls
364 lines (331 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
/**
* Global singleton cache for Perps signing operations
*
* This cache persists across provider reconnections to prevent repeated
* signing requests for hardware wallets. Critical for preventing repeated
* hardware wallet signing prompts.
*
* Cache is intentionally kept separate from provider instances because providers
* are recreated on account/network changes, which would reset instance-level caches.
*
* Tracks three signing operations:
* 1. Unified Account enablement (one-time, replaces deprecated DEX abstraction)
* 2. Builder Fee approval (required for trading)
* 3. Referral code setup (one-time per account)
*
* Cache Structure:
* - Key: `network:userAddress` (e.g., "mainnet:0x123...")
* - Value: { unifiedAccount, builderFee, referral, timestamp }
*
* Lifecycle:
* - Cache persists throughout app session
* - Individual entries can be cleared per user/network
* - Full cache can be cleared on app restart or explicit user action
*/
type SigningOperationState = {
attempted: boolean; // Whether we've attempted this operation
success: boolean; // Whether it succeeded (only valid if attempted=true)
};
type PerpsSigningCacheEntry = {
unifiedAccount: SigningOperationState;
builderFee: SigningOperationState;
referral: SigningOperationState;
timestamp: number; // When this entry was last updated
};
// Legacy interface for backward compatibility
type TradingReadinessCacheEntry = {
attempted: boolean;
enabled: boolean;
timestamp: number;
};
class PerpsSigningCacheManager {
static #instance: PerpsSigningCacheManager;
readonly #cache: Map<string, PerpsSigningCacheEntry> = new Map();
// Global in-flight locks to prevent concurrent signing attempts across providers
// Key: operationType:network:userAddress, Value: Promise that resolves when operation completes
readonly #inFlightOperations: Map<string, Promise<void>> = new Map();
// Singleton: use getInstance() instead of new
protected constructor() {
// Protected constructor for singleton
}
public static getInstance(): PerpsSigningCacheManager {
PerpsSigningCacheManager.#instance ??= new PerpsSigningCacheManager();
return PerpsSigningCacheManager.#instance;
}
// ===== In-Flight Lock Methods =====
/**
* Check if an operation is currently in-flight for this user/network
*
* @param operationType - The type of operation being performed.
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @returns The resulting string value.
*/
public isInFlight(
operationType: 'unifiedAccount' | 'builderFee' | 'referral',
network: 'mainnet' | 'testnet',
userAddress: string,
): Promise<void> | undefined {
const key = `${operationType}:${network}:${userAddress.toLowerCase()}`;
return this.#inFlightOperations.get(key);
}
/**
* Set an operation as in-flight
* Returns a function to call when operation completes
*
* @param operationType - The type of operation being performed.
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @returns The resulting string value.
*/
public setInFlight(
operationType: 'unifiedAccount' | 'builderFee' | 'referral',
network: 'mainnet' | 'testnet',
userAddress: string,
): () => void {
const key = `${operationType}:${network}:${userAddress.toLowerCase()}`;
let resolvePromise: () => void;
const promise = new Promise<void>((resolve) => {
resolvePromise = resolve;
});
this.#inFlightOperations.set(key, promise);
return () => {
this.#inFlightOperations.delete(key);
resolvePromise();
};
}
#getCacheKey(network: 'mainnet' | 'testnet', userAddress: string): string {
return `${network}:${userAddress.toLowerCase()}`;
}
#getOrCreateEntry(
network: 'mainnet' | 'testnet',
userAddress: string,
): PerpsSigningCacheEntry {
const key = this.#getCacheKey(network, userAddress);
let entry = this.#cache.get(key);
if (!entry) {
entry = {
unifiedAccount: { attempted: false, success: false },
builderFee: { attempted: false, success: false },
referral: { attempted: false, success: false },
timestamp: Date.now(),
};
this.#cache.set(key, entry);
}
return entry;
}
// ===== Unified Account Methods =====
/**
* Get unified account cache entry (legacy compatibility)
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @returns The resulting string value.
*/
public get(
network: 'mainnet' | 'testnet',
userAddress: string,
): TradingReadinessCacheEntry | undefined {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
if (!entry) {
return undefined;
}
return {
attempted: entry.unifiedAccount.attempted,
enabled: entry.unifiedAccount.success,
timestamp: entry.timestamp,
};
}
/**
* Set unified account cache entry (legacy compatibility)
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @param data - The transaction data payload.
* @param data.attempted - Whether the operation was attempted.
* @param data.enabled - Whether the feature is enabled.
*/
public set(
network: 'mainnet' | 'testnet',
userAddress: string,
data: { attempted: boolean; enabled: boolean },
): void {
const entry = this.#getOrCreateEntry(network, userAddress);
entry.unifiedAccount = { attempted: data.attempted, success: data.enabled };
entry.timestamp = Date.now();
}
// ===== Builder Fee Methods =====
/**
* Check if builder fee approval was attempted
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @returns The resulting string value.
*/
public getBuilderFee(
network: 'mainnet' | 'testnet',
userAddress: string,
): SigningOperationState | undefined {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
return entry?.builderFee;
}
/**
* Set builder fee approval state
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @param state - The current state.
*/
public setBuilderFee(
network: 'mainnet' | 'testnet',
userAddress: string,
state: SigningOperationState,
): void {
const entry = this.#getOrCreateEntry(network, userAddress);
entry.builderFee = state;
entry.timestamp = Date.now();
}
// ===== Referral Methods =====
/**
* Check if referral setup was attempted
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @returns The resulting string value.
*/
public getReferral(
network: 'mainnet' | 'testnet',
userAddress: string,
): SigningOperationState | undefined {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
return entry?.referral;
}
/**
* Set referral setup state
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
* @param state - The current state.
*/
public setReferral(
network: 'mainnet' | 'testnet',
userAddress: string,
state: SigningOperationState,
): void {
const entry = this.#getOrCreateEntry(network, userAddress);
entry.referral = state;
entry.timestamp = Date.now();
}
// ===== General Methods =====
/**
* Clear only unified account state for a specific network and user address
* This preserves builder fee and referral states
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
*/
public clearUnifiedAccount(
network: 'mainnet' | 'testnet',
userAddress: string,
): void {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
if (entry) {
entry.unifiedAccount = { attempted: false, success: false };
entry.timestamp = Date.now();
}
}
/**
* Clear only builder fee state for a specific network and user address
* This preserves unified account and referral states
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
*/
public clearBuilderFee(
network: 'mainnet' | 'testnet',
userAddress: string,
): void {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
if (entry) {
entry.builderFee = { attempted: false, success: false };
entry.timestamp = Date.now();
}
}
/**
* Clear only referral state for a specific network and user address
* This preserves unified account and builder fee states
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
*/
public clearReferral(
network: 'mainnet' | 'testnet',
userAddress: string,
): void {
const key = this.#getCacheKey(network, userAddress);
const entry = this.#cache.get(key);
if (entry) {
entry.referral = { attempted: false, success: false };
entry.timestamp = Date.now();
}
}
/**
* Clear entire cache entry for a specific network and user address
* WARNING: This clears ALL signing operation states (unifiedAccount, builderFee, referral)
*
* @param network - The network environment.
* @param userAddress - The user's wallet address.
*/
public clear(network: 'mainnet' | 'testnet', userAddress: string): void {
const key = this.#getCacheKey(network, userAddress);
this.#cache.delete(key);
}
/**
* Clear all cache entries
* WARNING: This clears ALL signing operation states for ALL users
*/
public clearAll(): void {
this.#cache.clear();
}
/**
* Get all cache entries (for debugging)
*
* @returns The result of the operation.
*/
public getAll(): Map<string, PerpsSigningCacheEntry> {
return new Map(this.#cache);
}
/**
* Get cache size (for debugging)
*
* @returns The resulting numeric value.
*/
public size(): number {
return this.#cache.size;
}
/**
* Get full cache state for debugging
*
* @returns The resulting string value.
*/
public debugState(): string {
const entries: string[] = [];
this.#cache.forEach((entry, key) => {
entries.push(
`${key}: unified=${entry.unifiedAccount.attempted}/${entry.unifiedAccount.success}, ` +
`builder=${entry.builderFee.attempted}/${entry.builderFee.success}, ` +
`referral=${entry.referral.attempted}/${entry.referral.success}`,
);
});
return entries.join('\n') || '(empty)';
}
}
// Export singleton instance with backward-compatible name
export const TradingReadinessCache = PerpsSigningCacheManager.getInstance();
// Export with new name for clarity
export const PerpsSigningCache = PerpsSigningCacheManager.getInstance();