@usearete/react 0.1.5 → 0.3.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/README.md +350 -61
- package/dist/index.cjs +2344 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +516 -66
- package/dist/index.esm.js +1800 -9653
- package/dist/index.esm.js.map +1 -1
- package/eslint-plugin.cjs +5 -0
- package/eslint-rules/arete-hooks.js +171 -0
- package/eslint-rules/package.json +3 -0
- package/package.json +18 -9
- package/dist/index.js +0 -10033
- package/dist/index.js.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2344 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React = require('react');
|
|
4
|
+
var sdk = require('@usearete/sdk');
|
|
5
|
+
var zustand = require('zustand');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 16;
|
|
8
|
+
|
|
9
|
+
class ZustandAdapter {
|
|
10
|
+
constructor(_config = {}) {
|
|
11
|
+
this.updateCallbacks = new Set();
|
|
12
|
+
this.richUpdateCallbacks = new Set();
|
|
13
|
+
this.accessOrder = new Map();
|
|
14
|
+
this.store = zustand.create((set) => ({
|
|
15
|
+
entities: new Map(),
|
|
16
|
+
viewConfigs: new Map(),
|
|
17
|
+
connectionState: 'disconnected',
|
|
18
|
+
lastError: undefined,
|
|
19
|
+
_set: (viewPath, key, data) => {
|
|
20
|
+
set((state) => {
|
|
21
|
+
const newEntities = new Map(state.entities);
|
|
22
|
+
const viewMap = new Map(newEntities.get(viewPath) ?? new Map());
|
|
23
|
+
viewMap.set(key, data);
|
|
24
|
+
newEntities.set(viewPath, viewMap);
|
|
25
|
+
return { entities: newEntities };
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
_delete: (viewPath, key) => {
|
|
29
|
+
set((state) => {
|
|
30
|
+
const newEntities = new Map(state.entities);
|
|
31
|
+
const viewMap = newEntities.get(viewPath);
|
|
32
|
+
if (viewMap) {
|
|
33
|
+
const newViewMap = new Map(viewMap);
|
|
34
|
+
newViewMap.delete(key);
|
|
35
|
+
newEntities.set(viewPath, newViewMap);
|
|
36
|
+
}
|
|
37
|
+
return { entities: newEntities };
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
_clear: (viewPath) => {
|
|
41
|
+
set((state) => {
|
|
42
|
+
if (viewPath) {
|
|
43
|
+
const newEntities = new Map(state.entities);
|
|
44
|
+
newEntities.delete(viewPath);
|
|
45
|
+
return { entities: newEntities };
|
|
46
|
+
}
|
|
47
|
+
return { entities: new Map() };
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
_setConnectionState: (connectionState, lastError) => {
|
|
51
|
+
set({ connectionState, lastError });
|
|
52
|
+
},
|
|
53
|
+
_setViewConfig: (viewPath, config) => {
|
|
54
|
+
set((state) => {
|
|
55
|
+
const newConfigs = new Map(state.viewConfigs);
|
|
56
|
+
newConfigs.set(viewPath, config);
|
|
57
|
+
return { viewConfigs: newConfigs };
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
get(viewPath, key) {
|
|
63
|
+
const viewMap = this.store.getState().entities.get(viewPath);
|
|
64
|
+
if (!viewMap)
|
|
65
|
+
return null;
|
|
66
|
+
const value = viewMap.get(key);
|
|
67
|
+
return value !== undefined ? value : null;
|
|
68
|
+
}
|
|
69
|
+
getAll(viewPath) {
|
|
70
|
+
const viewMap = this.store.getState().entities.get(viewPath);
|
|
71
|
+
if (!viewMap)
|
|
72
|
+
return [];
|
|
73
|
+
return Array.from(viewMap.values());
|
|
74
|
+
}
|
|
75
|
+
getAllSync(viewPath) {
|
|
76
|
+
const viewMap = this.store.getState().entities.get(viewPath);
|
|
77
|
+
if (!viewMap)
|
|
78
|
+
return undefined;
|
|
79
|
+
return Array.from(viewMap.values());
|
|
80
|
+
}
|
|
81
|
+
getSync(viewPath, key) {
|
|
82
|
+
const viewMap = this.store.getState().entities.get(viewPath);
|
|
83
|
+
if (!viewMap)
|
|
84
|
+
return undefined;
|
|
85
|
+
const value = viewMap.get(key);
|
|
86
|
+
return value !== undefined ? value : null;
|
|
87
|
+
}
|
|
88
|
+
has(viewPath, key) {
|
|
89
|
+
return this.store.getState().entities.get(viewPath)?.has(key) ?? false;
|
|
90
|
+
}
|
|
91
|
+
keys(viewPath) {
|
|
92
|
+
const viewMap = this.store.getState().entities.get(viewPath);
|
|
93
|
+
if (!viewMap)
|
|
94
|
+
return [];
|
|
95
|
+
return Array.from(viewMap.keys());
|
|
96
|
+
}
|
|
97
|
+
size(viewPath) {
|
|
98
|
+
return this.store.getState().entities.get(viewPath)?.size ?? 0;
|
|
99
|
+
}
|
|
100
|
+
set(viewPath, key, data) {
|
|
101
|
+
let order = this.accessOrder.get(viewPath);
|
|
102
|
+
if (!order) {
|
|
103
|
+
order = [];
|
|
104
|
+
this.accessOrder.set(viewPath, order);
|
|
105
|
+
}
|
|
106
|
+
const existingIdx = order.indexOf(key);
|
|
107
|
+
if (existingIdx !== -1) {
|
|
108
|
+
order.splice(existingIdx, 1);
|
|
109
|
+
}
|
|
110
|
+
order.push(key);
|
|
111
|
+
this.store.getState()._set(viewPath, key, data);
|
|
112
|
+
}
|
|
113
|
+
delete(viewPath, key) {
|
|
114
|
+
const order = this.accessOrder.get(viewPath);
|
|
115
|
+
if (order) {
|
|
116
|
+
const idx = order.indexOf(key);
|
|
117
|
+
if (idx !== -1) {
|
|
118
|
+
order.splice(idx, 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this.store.getState()._delete(viewPath, key);
|
|
122
|
+
}
|
|
123
|
+
clear(viewPath) {
|
|
124
|
+
if (viewPath) {
|
|
125
|
+
this.accessOrder.delete(viewPath);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.accessOrder.clear();
|
|
129
|
+
}
|
|
130
|
+
this.store.getState()._clear(viewPath);
|
|
131
|
+
}
|
|
132
|
+
evictOldest(viewPath) {
|
|
133
|
+
const order = this.accessOrder.get(viewPath);
|
|
134
|
+
if (!order || order.length === 0)
|
|
135
|
+
return undefined;
|
|
136
|
+
const oldest = order.shift();
|
|
137
|
+
if (oldest !== undefined) {
|
|
138
|
+
this.store.getState()._delete(viewPath, oldest);
|
|
139
|
+
}
|
|
140
|
+
return oldest;
|
|
141
|
+
}
|
|
142
|
+
onUpdate(callback) {
|
|
143
|
+
this.updateCallbacks.add(callback);
|
|
144
|
+
return () => this.updateCallbacks.delete(callback);
|
|
145
|
+
}
|
|
146
|
+
onRichUpdate(callback) {
|
|
147
|
+
this.richUpdateCallbacks.add(callback);
|
|
148
|
+
return () => this.richUpdateCallbacks.delete(callback);
|
|
149
|
+
}
|
|
150
|
+
notifyUpdate(viewPath, key, update) {
|
|
151
|
+
for (const callback of this.updateCallbacks) {
|
|
152
|
+
callback(viewPath, key, update);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
notifyRichUpdate(viewPath, key, update) {
|
|
156
|
+
for (const callback of this.richUpdateCallbacks) {
|
|
157
|
+
callback(viewPath, key, update);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
setConnectionState(state, error) {
|
|
161
|
+
this.store.getState()._setConnectionState(state, error);
|
|
162
|
+
}
|
|
163
|
+
setViewConfig(viewPath, config) {
|
|
164
|
+
const state = this.store.getState();
|
|
165
|
+
const existingConfig = state.viewConfigs.get(viewPath);
|
|
166
|
+
if (existingConfig)
|
|
167
|
+
return;
|
|
168
|
+
state._setViewConfig(viewPath, config);
|
|
169
|
+
}
|
|
170
|
+
getViewConfig(viewPath) {
|
|
171
|
+
return this.store.getState().viewConfigs.get(viewPath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function syncClientWallets(entries, wallet) {
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
entry.client.setWallet(wallet);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function initializeConnectedClient(client, adapter, wallet) {
|
|
181
|
+
client.setWallet(wallet);
|
|
182
|
+
client.onConnectionStateChange((state) => {
|
|
183
|
+
adapter.setConnectionState(state);
|
|
184
|
+
});
|
|
185
|
+
adapter.setConnectionState(client.connectionState);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const objectKeys = new WeakMap();
|
|
189
|
+
let nextObjectKey = 0;
|
|
190
|
+
function getObjectKey(value, prefix) {
|
|
191
|
+
const existing = objectKeys.get(value);
|
|
192
|
+
if (existing) {
|
|
193
|
+
return existing;
|
|
194
|
+
}
|
|
195
|
+
const key = `${prefix}-${++nextObjectKey}`;
|
|
196
|
+
objectKeys.set(value, key);
|
|
197
|
+
return key;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Generated programs share by their behavior fingerprint. Manual definitions
|
|
201
|
+
* fall back to object identity so definitions with unknown behavior never
|
|
202
|
+
* accidentally share a client.
|
|
203
|
+
*/
|
|
204
|
+
function getProgramsKey(programs) {
|
|
205
|
+
const entries = Object.entries(programs)
|
|
206
|
+
.map(([key, definition]) => [
|
|
207
|
+
key,
|
|
208
|
+
definition.definitionHash
|
|
209
|
+
?? getObjectKey(definition, 'program'),
|
|
210
|
+
])
|
|
211
|
+
.sort(([left], [right]) => left.localeCompare(right));
|
|
212
|
+
return entries.length > 0 ? JSON.stringify(entries) : 'programs-empty';
|
|
213
|
+
}
|
|
214
|
+
function createClientCacheKey(stack, options) {
|
|
215
|
+
if (!stack) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
return [
|
|
219
|
+
getObjectKey(stack, 'stack'),
|
|
220
|
+
options?.transport ?? 'ws',
|
|
221
|
+
options?.url ?? stack.endpoints.ws,
|
|
222
|
+
options?.httpUrl ?? stack.endpoints.http ?? '',
|
|
223
|
+
options?.programs
|
|
224
|
+
? getProgramsKey(options.programs)
|
|
225
|
+
: 'programs-none',
|
|
226
|
+
].join(':');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function trackConnectingPromise(connecting, cacheKey, promise) {
|
|
230
|
+
connecting.set(cacheKey, promise);
|
|
231
|
+
void promise.catch(() => {
|
|
232
|
+
if (connecting.get(cacheKey) === promise) {
|
|
233
|
+
connecting.delete(cacheKey);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
return promise;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const AreteContext = React.createContext(null);
|
|
240
|
+
function AreteProvider({ children, ...config }) {
|
|
241
|
+
const clientsRef = React.useRef(new Map());
|
|
242
|
+
const connectingRef = React.useRef(new Map());
|
|
243
|
+
const clientChangeListenersRef = React.useRef(new Set());
|
|
244
|
+
const latestWalletRef = React.useRef(config.wallet);
|
|
245
|
+
const lifecycleRef = React.useRef({ active: true, generation: 0 });
|
|
246
|
+
latestWalletRef.current = config.wallet;
|
|
247
|
+
const notifyClientChange = React.useCallback((change) => {
|
|
248
|
+
clientChangeListenersRef.current.forEach(cb => { cb(change); });
|
|
249
|
+
}, []);
|
|
250
|
+
const subscribeToClientChanges = React.useCallback((callback) => {
|
|
251
|
+
clientChangeListenersRef.current.add(callback);
|
|
252
|
+
return () => {
|
|
253
|
+
clientChangeListenersRef.current.delete(callback);
|
|
254
|
+
};
|
|
255
|
+
}, []);
|
|
256
|
+
const getOrCreateClient = React.useCallback(async (stack, options) => {
|
|
257
|
+
const cacheKey = createClientCacheKey(stack, options);
|
|
258
|
+
if (!cacheKey) {
|
|
259
|
+
throw new Error('Stack is required to create an Arete client');
|
|
260
|
+
}
|
|
261
|
+
const existing = clientsRef.current.get(cacheKey);
|
|
262
|
+
if (existing) {
|
|
263
|
+
return existing.client;
|
|
264
|
+
}
|
|
265
|
+
const connecting = connectingRef.current.get(cacheKey);
|
|
266
|
+
if (connecting) {
|
|
267
|
+
return connecting;
|
|
268
|
+
}
|
|
269
|
+
const adapter = new ZustandAdapter();
|
|
270
|
+
const lifecycleGeneration = lifecycleRef.current.generation;
|
|
271
|
+
let connectionPromise;
|
|
272
|
+
connectionPromise = sdk.Arete.connect(stack, {
|
|
273
|
+
url: options?.url,
|
|
274
|
+
httpUrl: options?.httpUrl,
|
|
275
|
+
transport: options?.transport,
|
|
276
|
+
programs: options?.programs,
|
|
277
|
+
storage: adapter,
|
|
278
|
+
autoConnect: config.autoConnect,
|
|
279
|
+
autoReconnect: config.autoReconnect,
|
|
280
|
+
reconnectIntervals: config.reconnectIntervals,
|
|
281
|
+
maxReconnectAttempts: config.maxReconnectAttempts,
|
|
282
|
+
maxEntriesPerView: config.maxEntriesPerView,
|
|
283
|
+
flushIntervalMs: config.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
284
|
+
fetch: config.fetch,
|
|
285
|
+
validateFrames: config.validateFrames,
|
|
286
|
+
onFrameValidationError: config.onFrameValidationError,
|
|
287
|
+
auth: config.auth,
|
|
288
|
+
wallet: latestWalletRef.current,
|
|
289
|
+
}).then((client) => {
|
|
290
|
+
const ownsAttempt = lifecycleRef.current.active
|
|
291
|
+
&& lifecycleRef.current.generation === lifecycleGeneration
|
|
292
|
+
&& connectingRef.current.get(cacheKey) === connectionPromise;
|
|
293
|
+
if (!ownsAttempt) {
|
|
294
|
+
client.disconnect();
|
|
295
|
+
throw new Error('Arete connection attempt was superseded');
|
|
296
|
+
}
|
|
297
|
+
initializeConnectedClient(client, adapter, latestWalletRef.current);
|
|
298
|
+
clientsRef.current.set(cacheKey, {
|
|
299
|
+
client: client,
|
|
300
|
+
disconnect: () => client.disconnect()
|
|
301
|
+
});
|
|
302
|
+
connectingRef.current.delete(cacheKey);
|
|
303
|
+
notifyClientChange({ cacheKey, status: 'connected' });
|
|
304
|
+
return client;
|
|
305
|
+
}).catch((value) => {
|
|
306
|
+
const error = value instanceof Error ? value : new Error(String(value));
|
|
307
|
+
if (connectingRef.current.get(cacheKey) === connectionPromise) {
|
|
308
|
+
connectingRef.current.delete(cacheKey);
|
|
309
|
+
if (lifecycleRef.current.active
|
|
310
|
+
&& lifecycleRef.current.generation === lifecycleGeneration) {
|
|
311
|
+
notifyClientChange({ cacheKey, status: 'error', error });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
});
|
|
316
|
+
trackConnectingPromise(connectingRef.current, cacheKey, connectionPromise);
|
|
317
|
+
notifyClientChange({ cacheKey, status: 'connecting' });
|
|
318
|
+
return connectionPromise;
|
|
319
|
+
}, [config.autoConnect, config.autoReconnect, config.reconnectIntervals, config.maxReconnectAttempts, config.maxEntriesPerView, config.flushIntervalMs, config.fetch, config.validateFrames, config.onFrameValidationError, config.auth, notifyClientChange]);
|
|
320
|
+
const getClient = React.useCallback((stack, options) => {
|
|
321
|
+
if (!stack) {
|
|
322
|
+
if (clientsRef.current.size === 1) {
|
|
323
|
+
const firstEntry = clientsRef.current.values().next().value;
|
|
324
|
+
return firstEntry ? firstEntry.client : null;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const cacheKey = createClientCacheKey(stack, options);
|
|
329
|
+
if (!cacheKey) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
const entry = clientsRef.current.get(cacheKey);
|
|
333
|
+
return entry ? entry.client : null;
|
|
334
|
+
}, []);
|
|
335
|
+
const retryClient = React.useCallback((stack, options) => {
|
|
336
|
+
const cacheKey = createClientCacheKey(stack, options);
|
|
337
|
+
if (!cacheKey) {
|
|
338
|
+
return Promise.reject(new Error('Stack is required to retry an Arete client'));
|
|
339
|
+
}
|
|
340
|
+
const existing = clientsRef.current.get(cacheKey);
|
|
341
|
+
if (existing) {
|
|
342
|
+
existing.disconnect();
|
|
343
|
+
clientsRef.current.delete(cacheKey);
|
|
344
|
+
}
|
|
345
|
+
return getOrCreateClient(stack, options);
|
|
346
|
+
}, [getOrCreateClient]);
|
|
347
|
+
React.useEffect(() => {
|
|
348
|
+
syncClientWallets(clientsRef.current.values(), config.wallet);
|
|
349
|
+
notifyClientChange();
|
|
350
|
+
}, [config.wallet, notifyClientChange]);
|
|
351
|
+
React.useEffect(() => {
|
|
352
|
+
const lifecycle = lifecycleRef.current;
|
|
353
|
+
const clients = clientsRef.current;
|
|
354
|
+
const connecting = connectingRef.current;
|
|
355
|
+
lifecycle.active = true;
|
|
356
|
+
return () => {
|
|
357
|
+
lifecycle.active = false;
|
|
358
|
+
lifecycle.generation++;
|
|
359
|
+
clients.forEach((entry) => {
|
|
360
|
+
entry.disconnect();
|
|
361
|
+
});
|
|
362
|
+
clients.clear();
|
|
363
|
+
connecting.clear();
|
|
364
|
+
};
|
|
365
|
+
}, []);
|
|
366
|
+
const value = {
|
|
367
|
+
getOrCreateClient,
|
|
368
|
+
getClient,
|
|
369
|
+
retryClient,
|
|
370
|
+
subscribeToClientChanges,
|
|
371
|
+
config,
|
|
372
|
+
};
|
|
373
|
+
return (React.createElement(AreteContext.Provider, { value: value }, children));
|
|
374
|
+
}
|
|
375
|
+
function useAreteContext() {
|
|
376
|
+
const context = React.useContext(AreteContext);
|
|
377
|
+
if (!context) {
|
|
378
|
+
throw new Error('useAreteContext must be used within AreteProvider');
|
|
379
|
+
}
|
|
380
|
+
return context;
|
|
381
|
+
}
|
|
382
|
+
function useConnectionState(stack, options) {
|
|
383
|
+
const { getClient, subscribeToClientChanges } = useAreteContext();
|
|
384
|
+
const [state, setState] = React.useState(() => {
|
|
385
|
+
const client = getClient(stack, options);
|
|
386
|
+
return client?.connectionState ?? 'disconnected';
|
|
387
|
+
});
|
|
388
|
+
const unsubscribeRef = React.useRef(undefined);
|
|
389
|
+
React.useEffect(() => {
|
|
390
|
+
let mounted = true;
|
|
391
|
+
const setupClientSubscription = () => {
|
|
392
|
+
unsubscribeRef.current?.();
|
|
393
|
+
unsubscribeRef.current = undefined;
|
|
394
|
+
const client = getClient(stack, options);
|
|
395
|
+
if (client && mounted) {
|
|
396
|
+
setState(client.connectionState);
|
|
397
|
+
unsubscribeRef.current = client.onConnectionStateChange((newState) => {
|
|
398
|
+
if (mounted)
|
|
399
|
+
setState(newState);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
else if (mounted) {
|
|
403
|
+
setState('disconnected');
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
const unsubscribeFromClientChanges = subscribeToClientChanges(setupClientSubscription);
|
|
407
|
+
setupClientSubscription();
|
|
408
|
+
return () => {
|
|
409
|
+
mounted = false;
|
|
410
|
+
unsubscribeFromClientChanges();
|
|
411
|
+
unsubscribeRef.current?.();
|
|
412
|
+
};
|
|
413
|
+
}, [getClient, subscribeToClientChanges, stack, options]);
|
|
414
|
+
return state;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function isMarkedDefaultReconciliation(value) {
|
|
418
|
+
return (typeof value === 'function'
|
|
419
|
+
&& value.areteDefaultReconciliation === true
|
|
420
|
+
&& typeof value.shouldReconcile === 'function'
|
|
421
|
+
&& typeof value.withOverrides === 'function');
|
|
422
|
+
}
|
|
423
|
+
function initialMutationState() {
|
|
424
|
+
return {
|
|
425
|
+
status: 'idle',
|
|
426
|
+
phase: 'idle',
|
|
427
|
+
latestEvent: null,
|
|
428
|
+
displayError: null,
|
|
429
|
+
failure: null,
|
|
430
|
+
outcome: null,
|
|
431
|
+
prepared: null,
|
|
432
|
+
result: undefined,
|
|
433
|
+
signatures: [],
|
|
434
|
+
completedReceipts: [],
|
|
435
|
+
callbackErrors: [],
|
|
436
|
+
reconciliationError: null,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function normalizeThrown(value) {
|
|
440
|
+
if (value instanceof Error) {
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
let message;
|
|
444
|
+
try {
|
|
445
|
+
message = typeof value === 'string' ? value : JSON.stringify(value);
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
message = String(value);
|
|
449
|
+
}
|
|
450
|
+
const error = new Error(message || String(value));
|
|
451
|
+
error.cause = value;
|
|
452
|
+
return error;
|
|
453
|
+
}
|
|
454
|
+
function displayErrorFor(error, failure) {
|
|
455
|
+
const unwrapped = sdk.unwrapOperationExecutionError(error);
|
|
456
|
+
if (unwrapped instanceof sdk.InstructionError && unwrapped.programError) {
|
|
457
|
+
return `${unwrapped.programError.name}: ${unwrapped.programError.message}`;
|
|
458
|
+
}
|
|
459
|
+
if (failure?.status === 'chain-failed' && failure.programError) {
|
|
460
|
+
return `${failure.programError.name}: ${failure.programError.message}`;
|
|
461
|
+
}
|
|
462
|
+
if (failure
|
|
463
|
+
&& failure.cause instanceof Error
|
|
464
|
+
&& failure.cause.message) {
|
|
465
|
+
return failure.cause.message;
|
|
466
|
+
}
|
|
467
|
+
return unwrapped instanceof Error ? unwrapped.message : error.message;
|
|
468
|
+
}
|
|
469
|
+
function isReceipt(value) {
|
|
470
|
+
if (!value || typeof value !== 'object') {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
const receipt = value;
|
|
474
|
+
return (typeof receipt.transactionIndex === 'number'
|
|
475
|
+
&& typeof receipt.transactionName === 'string'
|
|
476
|
+
&& typeof receipt.signature === 'string');
|
|
477
|
+
}
|
|
478
|
+
function receiptsFrom(value) {
|
|
479
|
+
if (!value || typeof value !== 'object') {
|
|
480
|
+
return [];
|
|
481
|
+
}
|
|
482
|
+
const candidate = value;
|
|
483
|
+
if (Array.isArray(candidate.completedReceipts)) {
|
|
484
|
+
return candidate.completedReceipts.filter(isReceipt);
|
|
485
|
+
}
|
|
486
|
+
if (Array.isArray(candidate.transactions)) {
|
|
487
|
+
return candidate.transactions.filter(isReceipt);
|
|
488
|
+
}
|
|
489
|
+
return isReceipt(candidate.transaction) ? [candidate.transaction] : [];
|
|
490
|
+
}
|
|
491
|
+
function signaturesFrom(value) {
|
|
492
|
+
if (!value || typeof value !== 'object') {
|
|
493
|
+
return [];
|
|
494
|
+
}
|
|
495
|
+
const candidate = value;
|
|
496
|
+
if (Array.isArray(candidate.signatures)) {
|
|
497
|
+
return candidate.signatures.filter((signature) => typeof signature === 'string');
|
|
498
|
+
}
|
|
499
|
+
return typeof candidate.signature === 'string' ? [candidate.signature] : [];
|
|
500
|
+
}
|
|
501
|
+
function callbackErrorsFrom(value) {
|
|
502
|
+
if (!value || typeof value !== 'object') {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const callbackErrors = value.callbackErrors;
|
|
506
|
+
return Array.isArray(callbackErrors) ? callbackErrors.map(normalizeThrown) : [];
|
|
507
|
+
}
|
|
508
|
+
function uniqueErrors(errors) {
|
|
509
|
+
return [...new Set(errors)];
|
|
510
|
+
}
|
|
511
|
+
function uniqueStrings(values) {
|
|
512
|
+
return [...new Set(values)];
|
|
513
|
+
}
|
|
514
|
+
function confirmedOutcome(signatures, receipts) {
|
|
515
|
+
const signature = signatures[signatures.length - 1];
|
|
516
|
+
if (!signature) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
const receipt = [...receipts].reverse().find((entry) => entry.signature === signature);
|
|
520
|
+
return {
|
|
521
|
+
status: 'confirmed',
|
|
522
|
+
phase: 'confirmation',
|
|
523
|
+
signature,
|
|
524
|
+
slot: receipt?.slot,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function unreconciledError(value) {
|
|
528
|
+
if (!value || typeof value !== 'object') {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const result = value;
|
|
532
|
+
if (result.status !== 'confirmed-unreconciled') {
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
return normalizeThrown(result.error ?? new Error('Confirmed transaction was not reconciled'));
|
|
536
|
+
}
|
|
537
|
+
function useInstructionMutation(execute, defaults) {
|
|
538
|
+
const [state, setState] = React.useState((initialMutationState));
|
|
539
|
+
const invocationRef = React.useRef(0);
|
|
540
|
+
const reconciliationAbortRef = React.useRef(null);
|
|
541
|
+
const reconciliationRetryRef = React.useRef(null);
|
|
542
|
+
const defaultsRef = React.useRef(defaults);
|
|
543
|
+
defaultsRef.current = defaults;
|
|
544
|
+
const submit = React.useCallback(async (args, options) => {
|
|
545
|
+
const invocation = ++invocationRef.current;
|
|
546
|
+
reconciliationAbortRef.current?.abort();
|
|
547
|
+
reconciliationRetryRef.current = null;
|
|
548
|
+
const reconciliationAbort = new AbortController();
|
|
549
|
+
reconciliationAbortRef.current = reconciliationAbort;
|
|
550
|
+
const updateCurrent = (update) => {
|
|
551
|
+
if (invocation === invocationRef.current) {
|
|
552
|
+
setState(update);
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const setPhase = (phase, patch = {}, transactionEvent) => {
|
|
556
|
+
updateCurrent((current) => ({
|
|
557
|
+
...current,
|
|
558
|
+
...patch,
|
|
559
|
+
phase,
|
|
560
|
+
latestEvent: {
|
|
561
|
+
phase,
|
|
562
|
+
prepared: patch.prepared === undefined ? current.prepared : patch.prepared,
|
|
563
|
+
...(patch.result === undefined ? {} : { result: patch.result }),
|
|
564
|
+
...(patch.failure ? { failure: patch.failure } : {}),
|
|
565
|
+
...(transactionEvent ? { transactionEvent } : {}),
|
|
566
|
+
},
|
|
567
|
+
}));
|
|
568
|
+
};
|
|
569
|
+
setState({
|
|
570
|
+
...initialMutationState(),
|
|
571
|
+
status: 'pending',
|
|
572
|
+
phase: 'preparing',
|
|
573
|
+
latestEvent: { phase: 'preparing', prepared: null },
|
|
574
|
+
});
|
|
575
|
+
const mergedOptions = {
|
|
576
|
+
...(defaultsRef.current ?? {}),
|
|
577
|
+
...(options ?? {}),
|
|
578
|
+
};
|
|
579
|
+
const onConfirmed = mergedOptions.onConfirmed;
|
|
580
|
+
const onSuccess = mergedOptions.onSuccess;
|
|
581
|
+
const onError = mergedOptions.onError;
|
|
582
|
+
const reconcileOption = mergedOptions.reconcile;
|
|
583
|
+
const executionOptions = { ...mergedOptions };
|
|
584
|
+
delete executionOptions.onConfirmed;
|
|
585
|
+
delete executionOptions.onSuccess;
|
|
586
|
+
delete executionOptions.onError;
|
|
587
|
+
delete executionOptions.reconcile;
|
|
588
|
+
let prepared = null;
|
|
589
|
+
const observedReceipts = [];
|
|
590
|
+
const observedCallbackErrors = [];
|
|
591
|
+
const observer = {
|
|
592
|
+
onPrepared(nextPrepared) {
|
|
593
|
+
prepared = nextPrepared;
|
|
594
|
+
setPhase('awaiting-wallet', { prepared: nextPrepared });
|
|
595
|
+
},
|
|
596
|
+
onAwaitingWallet() {
|
|
597
|
+
setPhase('awaiting-wallet');
|
|
598
|
+
},
|
|
599
|
+
onTransactionStart(event) {
|
|
600
|
+
setPhase('awaiting-wallet', {}, event);
|
|
601
|
+
},
|
|
602
|
+
onTransactionSuccess(event) {
|
|
603
|
+
observedReceipts.push(event.receipt);
|
|
604
|
+
const receipts = [...observedReceipts];
|
|
605
|
+
const signatures = uniqueStrings(receipts.map((receipt) => receipt.signature));
|
|
606
|
+
setPhase('submitted', {
|
|
607
|
+
completedReceipts: receipts,
|
|
608
|
+
signatures,
|
|
609
|
+
}, event);
|
|
610
|
+
},
|
|
611
|
+
onCallbackError(value) {
|
|
612
|
+
const error = normalizeThrown(value);
|
|
613
|
+
observedCallbackErrors.push(error);
|
|
614
|
+
updateCurrent((current) => ({
|
|
615
|
+
...current,
|
|
616
|
+
callbackErrors: uniqueErrors([...current.callbackErrors, error]),
|
|
617
|
+
}));
|
|
618
|
+
},
|
|
619
|
+
};
|
|
620
|
+
try {
|
|
621
|
+
let reconcile;
|
|
622
|
+
if (reconcileOption === false || reconcileOption === undefined) {
|
|
623
|
+
reconcile = undefined;
|
|
624
|
+
}
|
|
625
|
+
else if (typeof reconcileOption === 'function') {
|
|
626
|
+
reconcile = reconcileOption;
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
const hookDefault = defaultsRef.current?.reconcile;
|
|
630
|
+
if (isMarkedDefaultReconciliation(hookDefault)) {
|
|
631
|
+
reconcile = hookDefault.withOverrides(reconcileOption);
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
throw new Error('reconcile option objects require a generated program hook with default reconciliation; pass a reconcile function instead');
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const result = await execute(args, executionOptions, observer);
|
|
638
|
+
const completedReceipts = receiptsFrom(result).length > 0
|
|
639
|
+
? receiptsFrom(result)
|
|
640
|
+
: [...observedReceipts];
|
|
641
|
+
const signatures = uniqueStrings([
|
|
642
|
+
...signaturesFrom(result),
|
|
643
|
+
...completedReceipts.map((receipt) => receipt.signature),
|
|
644
|
+
]);
|
|
645
|
+
const outcome = confirmedOutcome(signatures, completedReceipts);
|
|
646
|
+
const callbackErrors = uniqueErrors([
|
|
647
|
+
...observedCallbackErrors,
|
|
648
|
+
...callbackErrorsFrom(result),
|
|
649
|
+
]);
|
|
650
|
+
const reconciliationContext = {
|
|
651
|
+
result,
|
|
652
|
+
prepared,
|
|
653
|
+
signatures,
|
|
654
|
+
completedReceipts,
|
|
655
|
+
signal: reconciliationAbort.signal,
|
|
656
|
+
};
|
|
657
|
+
const shouldReconcile = Boolean(reconcile
|
|
658
|
+
&& (!isMarkedDefaultReconciliation(reconcile)
|
|
659
|
+
|| reconcile.shouldReconcile(reconciliationContext)));
|
|
660
|
+
if (invocation === invocationRef.current && reconcile && shouldReconcile) {
|
|
661
|
+
reconciliationRetryRef.current = {
|
|
662
|
+
invocation,
|
|
663
|
+
reconcile,
|
|
664
|
+
context: {
|
|
665
|
+
result,
|
|
666
|
+
prepared,
|
|
667
|
+
signatures,
|
|
668
|
+
completedReceipts,
|
|
669
|
+
},
|
|
670
|
+
onSuccess,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
setPhase('confirmed', {
|
|
674
|
+
status: 'pending',
|
|
675
|
+
result,
|
|
676
|
+
outcome,
|
|
677
|
+
prepared,
|
|
678
|
+
signatures,
|
|
679
|
+
completedReceipts,
|
|
680
|
+
callbackErrors,
|
|
681
|
+
});
|
|
682
|
+
if (onConfirmed) {
|
|
683
|
+
try {
|
|
684
|
+
await onConfirmed(result);
|
|
685
|
+
}
|
|
686
|
+
catch (value) {
|
|
687
|
+
const callbackError = normalizeThrown(value);
|
|
688
|
+
observedCallbackErrors.push(callbackError);
|
|
689
|
+
updateCurrent((current) => ({
|
|
690
|
+
...current,
|
|
691
|
+
callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
|
|
692
|
+
}));
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (reconciliationAbort.signal.aborted || invocation !== invocationRef.current) {
|
|
696
|
+
return result;
|
|
697
|
+
}
|
|
698
|
+
let reconciled = !shouldReconcile;
|
|
699
|
+
if (reconcile && shouldReconcile) {
|
|
700
|
+
setPhase('reconciling', { status: 'pending' });
|
|
701
|
+
try {
|
|
702
|
+
const reconciliationResult = await reconcile(reconciliationContext);
|
|
703
|
+
const error = unreconciledError(reconciliationResult);
|
|
704
|
+
if (error) {
|
|
705
|
+
setPhase('confirmed-unreconciled', {
|
|
706
|
+
status: 'pending',
|
|
707
|
+
reconciliationError: error,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
reconciled = true;
|
|
712
|
+
if (reconciliationRetryRef.current?.invocation === invocation) {
|
|
713
|
+
reconciliationRetryRef.current = null;
|
|
714
|
+
}
|
|
715
|
+
setPhase('reconciled', { status: 'pending' });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
catch (value) {
|
|
719
|
+
const reconciliationError = normalizeThrown(value);
|
|
720
|
+
setPhase('confirmed-unreconciled', {
|
|
721
|
+
status: 'pending',
|
|
722
|
+
reconciliationError,
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (reconciled
|
|
727
|
+
&& onSuccess
|
|
728
|
+
&& !reconciliationAbort.signal.aborted
|
|
729
|
+
&& invocation === invocationRef.current) {
|
|
730
|
+
try {
|
|
731
|
+
await onSuccess(result);
|
|
732
|
+
}
|
|
733
|
+
catch (value) {
|
|
734
|
+
const callbackError = normalizeThrown(value);
|
|
735
|
+
observedCallbackErrors.push(callbackError);
|
|
736
|
+
updateCurrent((current) => ({
|
|
737
|
+
...current,
|
|
738
|
+
callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
|
|
739
|
+
}));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
updateCurrent((current) => ({ ...current, status: 'success' }));
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
catch (value) {
|
|
746
|
+
const error = normalizeThrown(value);
|
|
747
|
+
const failure = sdk.getTransactionFailureOutcome(error) ?? {
|
|
748
|
+
status: 'not-submitted',
|
|
749
|
+
phase: prepared ? 'send' : 'build',
|
|
750
|
+
cause: value,
|
|
751
|
+
};
|
|
752
|
+
const completedReceipts = value instanceof sdk.OperationExecutionError
|
|
753
|
+
? [...value.completedReceipts]
|
|
754
|
+
: receiptsFrom(value);
|
|
755
|
+
const signatures = uniqueStrings([
|
|
756
|
+
...completedReceipts.map((receipt) => receipt.signature),
|
|
757
|
+
...(failure && 'signature' in failure && failure.signature ? [failure.signature] : []),
|
|
758
|
+
]);
|
|
759
|
+
const callbackErrors = uniqueErrors([
|
|
760
|
+
...observedCallbackErrors,
|
|
761
|
+
...callbackErrorsFrom(value),
|
|
762
|
+
]);
|
|
763
|
+
const phase = failure?.status ?? 'not-submitted';
|
|
764
|
+
const displayError = displayErrorFor(error, failure);
|
|
765
|
+
setPhase(phase, {
|
|
766
|
+
status: 'pending',
|
|
767
|
+
displayError,
|
|
768
|
+
failure,
|
|
769
|
+
outcome: failure,
|
|
770
|
+
prepared: value instanceof sdk.OperationExecutionError
|
|
771
|
+
? value.operation
|
|
772
|
+
: prepared,
|
|
773
|
+
signatures,
|
|
774
|
+
completedReceipts,
|
|
775
|
+
callbackErrors,
|
|
776
|
+
});
|
|
777
|
+
if (onError) {
|
|
778
|
+
try {
|
|
779
|
+
await onError(error);
|
|
780
|
+
}
|
|
781
|
+
catch (callbackValue) {
|
|
782
|
+
const callbackError = normalizeThrown(callbackValue);
|
|
783
|
+
updateCurrent((current) => ({
|
|
784
|
+
...current,
|
|
785
|
+
callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
|
|
786
|
+
}));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
updateCurrent((current) => ({ ...current, status: 'error' }));
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
792
|
+
finally {
|
|
793
|
+
if (reconciliationAbortRef.current === reconciliationAbort) {
|
|
794
|
+
reconciliationAbortRef.current = null;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}, [execute]);
|
|
798
|
+
const retryReconciliation = React.useCallback(async () => {
|
|
799
|
+
const pending = reconciliationRetryRef.current;
|
|
800
|
+
if (!pending) {
|
|
801
|
+
throw new Error('No failed reconciliation is available to retry');
|
|
802
|
+
}
|
|
803
|
+
const invocation = ++invocationRef.current;
|
|
804
|
+
reconciliationAbortRef.current?.abort();
|
|
805
|
+
const reconciliationAbort = new AbortController();
|
|
806
|
+
reconciliationAbortRef.current = reconciliationAbort;
|
|
807
|
+
setState((current) => ({
|
|
808
|
+
...current,
|
|
809
|
+
status: 'pending',
|
|
810
|
+
phase: 'reconciling',
|
|
811
|
+
reconciliationError: null,
|
|
812
|
+
latestEvent: {
|
|
813
|
+
phase: 'reconciling',
|
|
814
|
+
prepared: pending.context.prepared,
|
|
815
|
+
result: pending.context.result,
|
|
816
|
+
},
|
|
817
|
+
}));
|
|
818
|
+
try {
|
|
819
|
+
const result = await pending.reconcile({
|
|
820
|
+
...pending.context,
|
|
821
|
+
signal: reconciliationAbort.signal,
|
|
822
|
+
});
|
|
823
|
+
const error = unreconciledError(result);
|
|
824
|
+
if (error)
|
|
825
|
+
throw error;
|
|
826
|
+
if (reconciliationAbort.signal.aborted || invocation !== invocationRef.current)
|
|
827
|
+
return;
|
|
828
|
+
reconciliationRetryRef.current = null;
|
|
829
|
+
setState((current) => ({
|
|
830
|
+
...current,
|
|
831
|
+
status: 'pending',
|
|
832
|
+
phase: 'reconciled',
|
|
833
|
+
reconciliationError: null,
|
|
834
|
+
latestEvent: {
|
|
835
|
+
phase: 'reconciled',
|
|
836
|
+
prepared: pending.context.prepared,
|
|
837
|
+
result: pending.context.result,
|
|
838
|
+
},
|
|
839
|
+
}));
|
|
840
|
+
if (pending.onSuccess) {
|
|
841
|
+
try {
|
|
842
|
+
await pending.onSuccess(pending.context.result);
|
|
843
|
+
}
|
|
844
|
+
catch (value) {
|
|
845
|
+
const callbackError = normalizeThrown(value);
|
|
846
|
+
if (invocation === invocationRef.current) {
|
|
847
|
+
setState((current) => ({
|
|
848
|
+
...current,
|
|
849
|
+
callbackErrors: uniqueErrors([...current.callbackErrors, callbackError]),
|
|
850
|
+
}));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
if (invocation === invocationRef.current) {
|
|
855
|
+
setState((current) => ({ ...current, status: 'success' }));
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
catch (value) {
|
|
859
|
+
const error = normalizeThrown(value);
|
|
860
|
+
if (!reconciliationAbort.signal.aborted && invocation === invocationRef.current) {
|
|
861
|
+
setState((current) => ({
|
|
862
|
+
...current,
|
|
863
|
+
status: 'success',
|
|
864
|
+
phase: 'confirmed-unreconciled',
|
|
865
|
+
reconciliationError: error,
|
|
866
|
+
latestEvent: {
|
|
867
|
+
phase: 'confirmed-unreconciled',
|
|
868
|
+
prepared: pending.context.prepared,
|
|
869
|
+
result: pending.context.result,
|
|
870
|
+
error,
|
|
871
|
+
},
|
|
872
|
+
}));
|
|
873
|
+
}
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
finally {
|
|
877
|
+
if (reconciliationAbortRef.current === reconciliationAbort) {
|
|
878
|
+
reconciliationAbortRef.current = null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}, []);
|
|
882
|
+
const reset = React.useCallback(() => {
|
|
883
|
+
invocationRef.current += 1;
|
|
884
|
+
reconciliationAbortRef.current?.abort();
|
|
885
|
+
reconciliationAbortRef.current = null;
|
|
886
|
+
reconciliationRetryRef.current = null;
|
|
887
|
+
setState(initialMutationState());
|
|
888
|
+
}, []);
|
|
889
|
+
const mutate = React.useCallback((args, options) => {
|
|
890
|
+
void submit(args, options).catch(() => undefined);
|
|
891
|
+
}, [submit]);
|
|
892
|
+
const signature = state.signatures.length === 1 ? state.signatures[0] : null;
|
|
893
|
+
return {
|
|
894
|
+
mutate,
|
|
895
|
+
mutateAsync: submit,
|
|
896
|
+
submit,
|
|
897
|
+
status: state.status,
|
|
898
|
+
phase: state.phase,
|
|
899
|
+
latestEvent: state.latestEvent,
|
|
900
|
+
error: state.displayError,
|
|
901
|
+
displayError: state.displayError,
|
|
902
|
+
failure: state.failure,
|
|
903
|
+
outcome: state.outcome,
|
|
904
|
+
prepared: state.prepared,
|
|
905
|
+
data: state.result,
|
|
906
|
+
result: state.result,
|
|
907
|
+
signatures: state.signatures,
|
|
908
|
+
signature,
|
|
909
|
+
completedReceipts: state.completedReceipts,
|
|
910
|
+
callbackError: state.callbackErrors[state.callbackErrors.length - 1] ?? null,
|
|
911
|
+
callbackErrors: state.callbackErrors,
|
|
912
|
+
reconciliationError: state.reconciliationError,
|
|
913
|
+
isLoading: state.status === 'pending',
|
|
914
|
+
isConfirmed: state.outcome?.status === 'confirmed',
|
|
915
|
+
isSubmittedUnknown: state.failure?.status === 'submitted-unknown',
|
|
916
|
+
isPreparing: state.phase === 'preparing',
|
|
917
|
+
isAwaitingWallet: state.phase === 'awaiting-wallet',
|
|
918
|
+
isReconciling: state.phase === 'reconciling',
|
|
919
|
+
canRetryReconciliation: state.phase === 'confirmed-unreconciled'
|
|
920
|
+
&& reconciliationRetryRef.current !== null,
|
|
921
|
+
retryReconciliation,
|
|
922
|
+
reset,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const DEFAULT_RECONCILIATION_TIMEOUT_MS = 30000;
|
|
927
|
+
function toSlot(slot) {
|
|
928
|
+
if (typeof slot === 'number') {
|
|
929
|
+
if (!Number.isSafeInteger(slot) || slot < 0) {
|
|
930
|
+
throw new RangeError('slot must be a non-negative safe integer or bigint');
|
|
931
|
+
}
|
|
932
|
+
return BigInt(slot);
|
|
933
|
+
}
|
|
934
|
+
if (slot < 0n) {
|
|
935
|
+
throw new RangeError('slot must be non-negative');
|
|
936
|
+
}
|
|
937
|
+
return slot;
|
|
938
|
+
}
|
|
939
|
+
function reconciliationError(value) {
|
|
940
|
+
if (value instanceof Error) {
|
|
941
|
+
return value;
|
|
942
|
+
}
|
|
943
|
+
const error = new Error(typeof value === 'string' ? value : String(value));
|
|
944
|
+
error.cause = value;
|
|
945
|
+
return error;
|
|
946
|
+
}
|
|
947
|
+
async function refreshTargets(targets, timeoutMs, signal) {
|
|
948
|
+
if (targets.length === 0)
|
|
949
|
+
return;
|
|
950
|
+
let timeout;
|
|
951
|
+
let onAbort;
|
|
952
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
953
|
+
timeout = setTimeout(() => {
|
|
954
|
+
reject(new Error(`Timed out waiting for refreshed Arete data after ${timeoutMs}ms`));
|
|
955
|
+
}, timeoutMs);
|
|
956
|
+
if (signal) {
|
|
957
|
+
onAbort = () => reject(reconciliationError(signal.reason ?? 'Reconciliation was cancelled'));
|
|
958
|
+
if (signal.aborted) {
|
|
959
|
+
onAbort();
|
|
960
|
+
}
|
|
961
|
+
else {
|
|
962
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
try {
|
|
967
|
+
await Promise.race([
|
|
968
|
+
Promise.all(targets.map((target) => typeof target === 'function' ? target() : target.refresh())),
|
|
969
|
+
interrupted,
|
|
970
|
+
]);
|
|
971
|
+
}
|
|
972
|
+
finally {
|
|
973
|
+
if (timeout !== undefined)
|
|
974
|
+
clearTimeout(timeout);
|
|
975
|
+
if (signal && onAbort)
|
|
976
|
+
signal.removeEventListener('abort', onAbort);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function reconcileProcessedSlot(client, slot, options = {}) {
|
|
980
|
+
const confirmedSlot = toSlot(slot);
|
|
981
|
+
if (!client) {
|
|
982
|
+
return {
|
|
983
|
+
status: 'confirmed-unreconciled',
|
|
984
|
+
confirmedSlot,
|
|
985
|
+
error: new Error('Arete client is not connected'),
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
try {
|
|
989
|
+
const processedSlot = await client.waitForProcessedSlot(confirmedSlot, {
|
|
990
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_RECONCILIATION_TIMEOUT_MS,
|
|
991
|
+
signal: options.signal,
|
|
992
|
+
});
|
|
993
|
+
const refreshes = Array.isArray(options.refresh)
|
|
994
|
+
? options.refresh
|
|
995
|
+
: options.refresh
|
|
996
|
+
? [options.refresh]
|
|
997
|
+
: [];
|
|
998
|
+
await refreshTargets(refreshes, options.timeoutMs ?? DEFAULT_RECONCILIATION_TIMEOUT_MS, options.signal);
|
|
999
|
+
return { status: 'reconciled', confirmedSlot, processedSlot };
|
|
1000
|
+
}
|
|
1001
|
+
catch (value) {
|
|
1002
|
+
return {
|
|
1003
|
+
status: 'confirmed-unreconciled',
|
|
1004
|
+
confirmedSlot,
|
|
1005
|
+
error: reconciliationError(value),
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
function useReconcileProcessedSlot(client) {
|
|
1010
|
+
return React.useCallback((slot, options) => reconcileProcessedSlot(client, slot, options), [client]);
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Build the default reconciliation for generated program hooks: wait until the
|
|
1014
|
+
* stream has processed the highest slot among completed transactions, then run
|
|
1015
|
+
* any requested refreshes. Mutations without a reported slot (e.g. raw
|
|
1016
|
+
* instruction sends) skip reconciliation silently.
|
|
1017
|
+
*/
|
|
1018
|
+
function createProcessedSlotReconciliation(client, baseOverrides = {}) {
|
|
1019
|
+
const build = (overrides) => {
|
|
1020
|
+
const receiptSlot = (context) => context.completedReceipts.reduce((highest, receipt) => receipt.slot === undefined
|
|
1021
|
+
? highest
|
|
1022
|
+
: highest === undefined
|
|
1023
|
+
? receipt.slot
|
|
1024
|
+
: Math.max(highest, receipt.slot), undefined);
|
|
1025
|
+
const reconcile = async (context) => {
|
|
1026
|
+
const slot = receiptSlot(context);
|
|
1027
|
+
if (slot === undefined) {
|
|
1028
|
+
return undefined;
|
|
1029
|
+
}
|
|
1030
|
+
return reconcileProcessedSlot(client, slot, {
|
|
1031
|
+
signal: context.signal,
|
|
1032
|
+
timeoutMs: overrides.timeoutMs,
|
|
1033
|
+
refresh: overrides.refresh,
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
return Object.assign(reconcile, {
|
|
1037
|
+
areteDefaultReconciliation: true,
|
|
1038
|
+
shouldReconcile: (context) => receiptSlot(context) !== undefined,
|
|
1039
|
+
withOverrides: (next) => build({ ...overrides, ...next }),
|
|
1040
|
+
});
|
|
1041
|
+
};
|
|
1042
|
+
return build(baseOverrides);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function composeCallbacks(observerCallback, callerCallback) {
|
|
1046
|
+
if (!observerCallback && !callerCallback)
|
|
1047
|
+
return undefined;
|
|
1048
|
+
return async (value) => {
|
|
1049
|
+
const errors = [];
|
|
1050
|
+
for (const callback of [observerCallback, callerCallback]) {
|
|
1051
|
+
if (!callback)
|
|
1052
|
+
continue;
|
|
1053
|
+
try {
|
|
1054
|
+
await callback(value);
|
|
1055
|
+
}
|
|
1056
|
+
catch (error) {
|
|
1057
|
+
errors.push(error);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (errors.length > 0)
|
|
1061
|
+
throw errors[0];
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Wrap the mutation factory so every generated program hook reconciles against
|
|
1066
|
+
* the stream by default: after confirmation, wait until the stack has
|
|
1067
|
+
* processed the transaction's slot. Callers opt out per hook or per submit
|
|
1068
|
+
* with `reconcile: false`, replace it with a function, or tweak it with a
|
|
1069
|
+
* `{ refresh, timeoutMs }` shorthand object.
|
|
1070
|
+
*/
|
|
1071
|
+
function withDefaultReconciliation(createMutationHook, client) {
|
|
1072
|
+
const defaultReconcile = client && typeof client.waitForProcessedSlot === 'function'
|
|
1073
|
+
? createProcessedSlotReconciliation(client)
|
|
1074
|
+
: undefined;
|
|
1075
|
+
if (!defaultReconcile) {
|
|
1076
|
+
return createMutationHook;
|
|
1077
|
+
}
|
|
1078
|
+
const factory = (execute, options) => {
|
|
1079
|
+
const callerReconcile = options?.reconcile;
|
|
1080
|
+
let reconcile = callerReconcile;
|
|
1081
|
+
if (callerReconcile !== false && typeof callerReconcile !== 'function') {
|
|
1082
|
+
reconcile = (callerReconcile
|
|
1083
|
+
? defaultReconcile.withOverrides(callerReconcile)
|
|
1084
|
+
: defaultReconcile);
|
|
1085
|
+
}
|
|
1086
|
+
return createMutationHook(execute, { ...options, reconcile });
|
|
1087
|
+
};
|
|
1088
|
+
return factory;
|
|
1089
|
+
}
|
|
1090
|
+
function wrapRawInstruction(instruction, client, createMutationHook) {
|
|
1091
|
+
const execute = async (input, options, observer) => {
|
|
1092
|
+
if (!client) {
|
|
1093
|
+
throw new Error('Arete client is not connected');
|
|
1094
|
+
}
|
|
1095
|
+
const built = instruction.build(input);
|
|
1096
|
+
observer?.onAwaitingWallet();
|
|
1097
|
+
return client.transaction([built], options);
|
|
1098
|
+
};
|
|
1099
|
+
return {
|
|
1100
|
+
execute,
|
|
1101
|
+
build: instruction.build,
|
|
1102
|
+
useMutation: (options) => createMutationHook(execute, options),
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
function isConnectedOperation(value) {
|
|
1106
|
+
return Boolean(value && typeof value === 'object' && typeof value.prepare === 'function');
|
|
1107
|
+
}
|
|
1108
|
+
function wrapOperation(operation, client, createMutationHook) {
|
|
1109
|
+
const execute = async (input, options, observer) => {
|
|
1110
|
+
if (!client) {
|
|
1111
|
+
throw new Error('Arete client is not connected');
|
|
1112
|
+
}
|
|
1113
|
+
const prepared = await operation.prepare(input);
|
|
1114
|
+
observer?.onPrepared(prepared);
|
|
1115
|
+
observer?.onAwaitingWallet();
|
|
1116
|
+
const executionOptions = options;
|
|
1117
|
+
return client.execute(prepared, {
|
|
1118
|
+
...(executionOptions ?? {}),
|
|
1119
|
+
onTransactionStart: composeCallbacks(observer ? (event) => observer.onTransactionStart(event) : undefined, executionOptions?.onTransactionStart),
|
|
1120
|
+
onTransactionSuccess: composeCallbacks(observer ? (event) => observer.onTransactionSuccess(event) : undefined, executionOptions?.onTransactionSuccess),
|
|
1121
|
+
onCallbackError: composeCallbacks(observer ? (error) => observer.onCallbackError(error) : undefined, executionOptions?.onCallbackError),
|
|
1122
|
+
});
|
|
1123
|
+
};
|
|
1124
|
+
return {
|
|
1125
|
+
prepare: operation.prepare.bind(operation),
|
|
1126
|
+
execute,
|
|
1127
|
+
useMutation: (options) => createMutationHook(execute, options),
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
function wrapOperationNamespace(namespace, client, createMutationHook) {
|
|
1131
|
+
const wrapped = {};
|
|
1132
|
+
for (const [name, value] of Object.entries(namespace)) {
|
|
1133
|
+
if (isConnectedOperation(value)) {
|
|
1134
|
+
wrapped[name] = wrapOperation(value, client, createMutationHook);
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (value && typeof value === 'object') {
|
|
1138
|
+
wrapped[name] = wrapOperationNamespace(value, client, createMutationHook);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return wrapped;
|
|
1142
|
+
}
|
|
1143
|
+
function wrapMaybeOperationNamespace(namespace, client, createMutationHook) {
|
|
1144
|
+
return namespace
|
|
1145
|
+
? wrapOperationNamespace(namespace, client, createMutationHook)
|
|
1146
|
+
: {};
|
|
1147
|
+
}
|
|
1148
|
+
const NOT_CONNECTED_ERROR = 'Arete client is not connected';
|
|
1149
|
+
function useDisconnectedMutation() {
|
|
1150
|
+
return useInstructionMutation(async () => {
|
|
1151
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Placeholder program hooks used before the client connects. Every namespace
|
|
1156
|
+
* path resolves (so `arete.programs.ore.transactions.mining.deploy.useMutation()`
|
|
1157
|
+
* is safe to call unconditionally) and every mutation hook keeps React state,
|
|
1158
|
+
* but preparing, executing, or submitting throws "not connected" — matching
|
|
1159
|
+
* what the real wrappers do. This lets components render their full tree
|
|
1160
|
+
* while the stack is offline instead of gating on `arete.client`.
|
|
1161
|
+
*/
|
|
1162
|
+
function buildDisconnectedProgramHooks() {
|
|
1163
|
+
const notConnected = () => {
|
|
1164
|
+
throw new Error(NOT_CONNECTED_ERROR);
|
|
1165
|
+
};
|
|
1166
|
+
const leaf = Object.assign(notConnected, {
|
|
1167
|
+
prepare: notConnected,
|
|
1168
|
+
execute: notConnected,
|
|
1169
|
+
send: notConnected,
|
|
1170
|
+
resolve: notConnected,
|
|
1171
|
+
plan: notConnected,
|
|
1172
|
+
build: notConnected,
|
|
1173
|
+
stage: notConnected,
|
|
1174
|
+
useMutation: () => useDisconnectedMutation(),
|
|
1175
|
+
});
|
|
1176
|
+
// eslint-disable-next-line prefer-const
|
|
1177
|
+
let proxy;
|
|
1178
|
+
proxy = new Proxy(leaf, {
|
|
1179
|
+
get(target, property) {
|
|
1180
|
+
if (typeof property === 'string' && !(property in target)) {
|
|
1181
|
+
return proxy;
|
|
1182
|
+
}
|
|
1183
|
+
return Reflect.get(target, property);
|
|
1184
|
+
},
|
|
1185
|
+
});
|
|
1186
|
+
return proxy;
|
|
1187
|
+
}
|
|
1188
|
+
function buildProgramHookInterfaces(programs, client, createMutationHook, options = {}) {
|
|
1189
|
+
const wrappedPrograms = {};
|
|
1190
|
+
if (!programs) {
|
|
1191
|
+
return wrappedPrograms;
|
|
1192
|
+
}
|
|
1193
|
+
const mutationHookFactory = options.defaultReconciliation === false
|
|
1194
|
+
? createMutationHook
|
|
1195
|
+
: withDefaultReconciliation(createMutationHook, client);
|
|
1196
|
+
for (const [name, program] of Object.entries(programs)) {
|
|
1197
|
+
const raw = {};
|
|
1198
|
+
for (const [instructionName, instruction] of Object.entries(program.raw)) {
|
|
1199
|
+
raw[instructionName] = wrapRawInstruction(instruction, client, mutationHookFactory);
|
|
1200
|
+
}
|
|
1201
|
+
wrappedPrograms[name] = {
|
|
1202
|
+
name: program.name,
|
|
1203
|
+
programId: program.programId,
|
|
1204
|
+
schemas: program.schemas,
|
|
1205
|
+
pdas: program.pdas,
|
|
1206
|
+
accounts: program.accounts,
|
|
1207
|
+
queries: program.queries,
|
|
1208
|
+
addresses: program.addresses,
|
|
1209
|
+
constants: program.constants,
|
|
1210
|
+
defaults: program.defaults,
|
|
1211
|
+
math: program.math,
|
|
1212
|
+
raw: raw,
|
|
1213
|
+
instructions: wrapMaybeOperationNamespace(program.instructions, client, mutationHookFactory),
|
|
1214
|
+
transactions: wrapMaybeOperationNamespace(program.transactions, client, mutationHookFactory),
|
|
1215
|
+
flows: wrapMaybeOperationNamespace(program.flows, client, mutationHookFactory),
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
return wrappedPrograms;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function stableKeyPart(value) {
|
|
1222
|
+
if (value === undefined)
|
|
1223
|
+
return 'undefined';
|
|
1224
|
+
if (value === null)
|
|
1225
|
+
return 'null';
|
|
1226
|
+
if (typeof value === 'bigint')
|
|
1227
|
+
return `bigint:${value}`;
|
|
1228
|
+
if (typeof value === 'string')
|
|
1229
|
+
return `string:${JSON.stringify(value)}`;
|
|
1230
|
+
if (typeof value === 'number')
|
|
1231
|
+
return `number:${String(value)}`;
|
|
1232
|
+
if (typeof value === 'boolean')
|
|
1233
|
+
return `boolean:${String(value)}`;
|
|
1234
|
+
if (Array.isArray(value)) {
|
|
1235
|
+
return `[${value.map(stableKeyPart).join(',')}]`;
|
|
1236
|
+
}
|
|
1237
|
+
const record = value;
|
|
1238
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableKeyPart(record[key])}`).join(',')}}`;
|
|
1239
|
+
}
|
|
1240
|
+
function normalizeReadError(value) {
|
|
1241
|
+
if (value instanceof Error) {
|
|
1242
|
+
return value;
|
|
1243
|
+
}
|
|
1244
|
+
const error = new Error(typeof value === 'string' ? value : String(value));
|
|
1245
|
+
error.cause = value;
|
|
1246
|
+
return error;
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Minimal keyed async state with no cache, retry, or request deduplication.
|
|
1250
|
+
* A newer request aborts and supersedes the previous request for this hook.
|
|
1251
|
+
*
|
|
1252
|
+
* The returned state is tagged with the key that produced it: when the key
|
|
1253
|
+
* changes, the hook immediately reports the fresh key's initial state instead
|
|
1254
|
+
* of briefly exposing data fetched for the previous key. Callers never need
|
|
1255
|
+
* to re-verify that `data` matches the arguments they passed.
|
|
1256
|
+
*/
|
|
1257
|
+
function useAsyncRead(key, read, options = {}) {
|
|
1258
|
+
const stableKey = stableKeyPart(key);
|
|
1259
|
+
const enabled = options.enabled ?? true;
|
|
1260
|
+
const [state, setState] = React.useState(() => ({
|
|
1261
|
+
key: stableKey,
|
|
1262
|
+
enabled,
|
|
1263
|
+
data: options.initialData,
|
|
1264
|
+
hasResolved: options.initialData !== undefined,
|
|
1265
|
+
error: null,
|
|
1266
|
+
isLoading: enabled && options.initialData === undefined,
|
|
1267
|
+
isRefreshing: false,
|
|
1268
|
+
}));
|
|
1269
|
+
const readRef = React.useRef(read);
|
|
1270
|
+
const keyRef = React.useRef(key);
|
|
1271
|
+
const enabledRef = React.useRef(enabled);
|
|
1272
|
+
const initialDataRef = React.useRef(options.initialData);
|
|
1273
|
+
const dataRef = React.useRef(options.initialData);
|
|
1274
|
+
const hasResolvedRef = React.useRef(options.initialData !== undefined);
|
|
1275
|
+
const requestRef = React.useRef({
|
|
1276
|
+
id: 0,
|
|
1277
|
+
controller: null,
|
|
1278
|
+
});
|
|
1279
|
+
const debounceTimerRef = React.useRef(null);
|
|
1280
|
+
const mountedRef = React.useRef(true);
|
|
1281
|
+
readRef.current = read;
|
|
1282
|
+
keyRef.current = key;
|
|
1283
|
+
enabledRef.current = enabled;
|
|
1284
|
+
initialDataRef.current = options.initialData;
|
|
1285
|
+
dataRef.current = state.data;
|
|
1286
|
+
const cancelCurrent = React.useCallback(() => {
|
|
1287
|
+
if (debounceTimerRef.current !== null) {
|
|
1288
|
+
clearTimeout(debounceTimerRef.current);
|
|
1289
|
+
debounceTimerRef.current = null;
|
|
1290
|
+
}
|
|
1291
|
+
requestRef.current.id += 1;
|
|
1292
|
+
requestRef.current.controller?.abort();
|
|
1293
|
+
requestRef.current.controller = null;
|
|
1294
|
+
}, []);
|
|
1295
|
+
const runRead = React.useCallback(async () => {
|
|
1296
|
+
if (debounceTimerRef.current !== null) {
|
|
1297
|
+
clearTimeout(debounceTimerRef.current);
|
|
1298
|
+
debounceTimerRef.current = null;
|
|
1299
|
+
}
|
|
1300
|
+
if (!enabledRef.current) {
|
|
1301
|
+
return dataRef.current;
|
|
1302
|
+
}
|
|
1303
|
+
const id = requestRef.current.id + 1;
|
|
1304
|
+
requestRef.current.id = id;
|
|
1305
|
+
requestRef.current.controller?.abort();
|
|
1306
|
+
const controller = new AbortController();
|
|
1307
|
+
requestRef.current.controller = controller;
|
|
1308
|
+
const hasData = hasResolvedRef.current;
|
|
1309
|
+
if (mountedRef.current) {
|
|
1310
|
+
setState((current) => ({
|
|
1311
|
+
...current,
|
|
1312
|
+
error: null,
|
|
1313
|
+
isLoading: !hasData,
|
|
1314
|
+
isRefreshing: hasData,
|
|
1315
|
+
}));
|
|
1316
|
+
}
|
|
1317
|
+
try {
|
|
1318
|
+
const data = await readRef.current({
|
|
1319
|
+
signal: controller.signal,
|
|
1320
|
+
key: keyRef.current,
|
|
1321
|
+
});
|
|
1322
|
+
if (mountedRef.current && requestRef.current.id === id) {
|
|
1323
|
+
dataRef.current = data;
|
|
1324
|
+
hasResolvedRef.current = true;
|
|
1325
|
+
setState({
|
|
1326
|
+
key: stableKeyPart(keyRef.current),
|
|
1327
|
+
enabled: true,
|
|
1328
|
+
data,
|
|
1329
|
+
hasResolved: true,
|
|
1330
|
+
error: null,
|
|
1331
|
+
isLoading: false,
|
|
1332
|
+
isRefreshing: false,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
return data;
|
|
1336
|
+
}
|
|
1337
|
+
catch (value) {
|
|
1338
|
+
const error = normalizeReadError(value);
|
|
1339
|
+
if (mountedRef.current
|
|
1340
|
+
&& requestRef.current.id === id
|
|
1341
|
+
&& !controller.signal.aborted) {
|
|
1342
|
+
setState((current) => ({
|
|
1343
|
+
...current,
|
|
1344
|
+
error,
|
|
1345
|
+
isLoading: false,
|
|
1346
|
+
isRefreshing: false,
|
|
1347
|
+
}));
|
|
1348
|
+
}
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1351
|
+
finally {
|
|
1352
|
+
if (requestRef.current.id === id) {
|
|
1353
|
+
requestRef.current.controller = null;
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}, []);
|
|
1357
|
+
React.useEffect(() => {
|
|
1358
|
+
mountedRef.current = true;
|
|
1359
|
+
return () => {
|
|
1360
|
+
mountedRef.current = false;
|
|
1361
|
+
};
|
|
1362
|
+
}, [cancelCurrent]);
|
|
1363
|
+
React.useEffect(() => {
|
|
1364
|
+
cancelCurrent();
|
|
1365
|
+
dataRef.current = initialDataRef.current;
|
|
1366
|
+
hasResolvedRef.current = initialDataRef.current !== undefined;
|
|
1367
|
+
setState({
|
|
1368
|
+
key: stableKey,
|
|
1369
|
+
enabled: enabledRef.current,
|
|
1370
|
+
data: initialDataRef.current,
|
|
1371
|
+
hasResolved: initialDataRef.current !== undefined,
|
|
1372
|
+
error: null,
|
|
1373
|
+
isLoading: enabledRef.current && initialDataRef.current === undefined,
|
|
1374
|
+
isRefreshing: false,
|
|
1375
|
+
});
|
|
1376
|
+
if (enabledRef.current) {
|
|
1377
|
+
const debounceMs = options.debounceMs ?? 0;
|
|
1378
|
+
if (debounceMs > 0) {
|
|
1379
|
+
debounceTimerRef.current = setTimeout(() => {
|
|
1380
|
+
debounceTimerRef.current = null;
|
|
1381
|
+
void runRead().catch(() => undefined);
|
|
1382
|
+
}, debounceMs);
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
void runRead().catch(() => undefined);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
return cancelCurrent;
|
|
1389
|
+
}, [cancelCurrent, runRead, stableKey, enabled, options.debounceMs]);
|
|
1390
|
+
// State committed under a different key must not leak into this render:
|
|
1391
|
+
// report the fresh key's initial state until the effect above commits.
|
|
1392
|
+
const visible = state.key === stableKey && state.enabled === enabled
|
|
1393
|
+
? state
|
|
1394
|
+
: {
|
|
1395
|
+
key: stableKey,
|
|
1396
|
+
enabled,
|
|
1397
|
+
data: options.initialData,
|
|
1398
|
+
hasResolved: options.initialData !== undefined,
|
|
1399
|
+
error: null,
|
|
1400
|
+
isLoading: enabled && options.initialData === undefined,
|
|
1401
|
+
isRefreshing: false,
|
|
1402
|
+
};
|
|
1403
|
+
const status = !enabled
|
|
1404
|
+
? options.disabledStatus ?? 'disabled'
|
|
1405
|
+
: visible.error
|
|
1406
|
+
? 'error'
|
|
1407
|
+
: visible.isLoading
|
|
1408
|
+
? 'loading'
|
|
1409
|
+
: visible.isRefreshing
|
|
1410
|
+
? 'refreshing'
|
|
1411
|
+
: 'ready';
|
|
1412
|
+
return {
|
|
1413
|
+
data: visible.data,
|
|
1414
|
+
error: visible.error,
|
|
1415
|
+
status,
|
|
1416
|
+
isPending: status === 'connecting' || status === 'loading',
|
|
1417
|
+
isReady: status === 'ready' || status === 'refreshing',
|
|
1418
|
+
isEmpty: visible.hasResolved
|
|
1419
|
+
&& (status === 'ready' || status === 'refreshing')
|
|
1420
|
+
&& visible.data == null,
|
|
1421
|
+
isLoading: visible.isLoading,
|
|
1422
|
+
isRefreshing: visible.isRefreshing,
|
|
1423
|
+
refresh: runRead,
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const readSourceIds = new WeakMap();
|
|
1428
|
+
let nextReadSourceId = 0;
|
|
1429
|
+
function readSourceId(read) {
|
|
1430
|
+
if (!read)
|
|
1431
|
+
return 0;
|
|
1432
|
+
const existing = readSourceIds.get(read);
|
|
1433
|
+
if (existing !== undefined)
|
|
1434
|
+
return existing;
|
|
1435
|
+
const id = ++nextReadSourceId;
|
|
1436
|
+
readSourceIds.set(read, id);
|
|
1437
|
+
return id;
|
|
1438
|
+
}
|
|
1439
|
+
/**
|
|
1440
|
+
* Wrap a stack's connected `read` functions in React hooks, mirroring how
|
|
1441
|
+
* program operations get `useMutation`. Each read exposes `.use(...args)`:
|
|
1442
|
+
* the args form the cache key and the read stays disabled while any required
|
|
1443
|
+
* arg is nullish, so `read.solClaimPreview.use(authority)` simply waits for a
|
|
1444
|
+
* connected wallet instead of needing non-null assertions and an `enabled`
|
|
1445
|
+
* flag.
|
|
1446
|
+
*
|
|
1447
|
+
* Hook-only options occupy the position after every declared read argument.
|
|
1448
|
+
* Optional read arguments must therefore be passed as `undefined` when options
|
|
1449
|
+
* follow them, keeping object-valued read arguments unambiguous.
|
|
1450
|
+
*
|
|
1451
|
+
* A proxy backs the interface so hooks exist (and stay disabled) before the
|
|
1452
|
+
* client connects — components can call them unconditionally.
|
|
1453
|
+
*/
|
|
1454
|
+
function buildReadInterfaces(read, readArgCounts) {
|
|
1455
|
+
return new Proxy({}, {
|
|
1456
|
+
get: (_target, name) => {
|
|
1457
|
+
if (typeof name !== 'string') {
|
|
1458
|
+
return undefined;
|
|
1459
|
+
}
|
|
1460
|
+
const readFn = read?.[name];
|
|
1461
|
+
const sourceId = readSourceId(readFn);
|
|
1462
|
+
const readArgCount = readArgCounts?.[name];
|
|
1463
|
+
const [requiredArgCount, totalArgCount] = Array.isArray(readArgCount)
|
|
1464
|
+
? readArgCount
|
|
1465
|
+
: [readArgCount, readArgCount];
|
|
1466
|
+
const useRead = (options, args) => {
|
|
1467
|
+
const hasArgs = args.length >= requiredArgCount
|
|
1468
|
+
&& args
|
|
1469
|
+
.slice(0, requiredArgCount)
|
|
1470
|
+
.every((arg) => arg !== undefined && arg !== null);
|
|
1471
|
+
return useAsyncRead([name, sourceId, ...args], async () => {
|
|
1472
|
+
if (!readFn) {
|
|
1473
|
+
throw new Error('Arete client is not connected');
|
|
1474
|
+
}
|
|
1475
|
+
return readFn(...args);
|
|
1476
|
+
}, {
|
|
1477
|
+
...options,
|
|
1478
|
+
enabled: readFn != null && hasArgs,
|
|
1479
|
+
disabledStatus: hasArgs && readFn == null ? 'connecting' : 'disabled',
|
|
1480
|
+
});
|
|
1481
|
+
};
|
|
1482
|
+
const use = (...argsAndOptions) => {
|
|
1483
|
+
if (!Number.isInteger(requiredArgCount)
|
|
1484
|
+
|| !Number.isInteger(totalArgCount)
|
|
1485
|
+
|| requiredArgCount < 0
|
|
1486
|
+
|| totalArgCount < requiredArgCount) {
|
|
1487
|
+
throw new Error(`Missing read argument count metadata for "${name}"`);
|
|
1488
|
+
}
|
|
1489
|
+
let options;
|
|
1490
|
+
let args = argsAndOptions;
|
|
1491
|
+
if (argsAndOptions.length <= totalArgCount) {
|
|
1492
|
+
options = undefined;
|
|
1493
|
+
}
|
|
1494
|
+
else if (argsAndOptions.length !== totalArgCount + 1) {
|
|
1495
|
+
throw new Error(`Read hook "${name}" expects ${totalArgCount} argument(s) followed by optional hook options`);
|
|
1496
|
+
}
|
|
1497
|
+
else {
|
|
1498
|
+
const candidate = argsAndOptions[totalArgCount];
|
|
1499
|
+
if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
1500
|
+
throw new Error(`Read hook options for "${name}" must be an object`);
|
|
1501
|
+
}
|
|
1502
|
+
options = candidate;
|
|
1503
|
+
args = argsAndOptions.slice(0, totalArgCount);
|
|
1504
|
+
}
|
|
1505
|
+
return useRead(options, args);
|
|
1506
|
+
};
|
|
1507
|
+
const imperative = (...args) => {
|
|
1508
|
+
if (!readFn) {
|
|
1509
|
+
throw new Error('Arete client is not connected');
|
|
1510
|
+
}
|
|
1511
|
+
return readFn(...args);
|
|
1512
|
+
};
|
|
1513
|
+
return Object.assign(imperative, {
|
|
1514
|
+
use,
|
|
1515
|
+
});
|
|
1516
|
+
},
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const noop = () => { };
|
|
1521
|
+
function queryError(value, fallback) {
|
|
1522
|
+
if (value instanceof sdk.AreteError)
|
|
1523
|
+
return value;
|
|
1524
|
+
return new sdk.AreteError(value instanceof Error ? value.message : fallback, 'SUBSCRIPTION_ERROR', value);
|
|
1525
|
+
}
|
|
1526
|
+
function useQuery(client, query, snapshotEnabled, enabled, initialData) {
|
|
1527
|
+
const queryKey = sdk.canonicalQueryKey({ query, snapshot: { enabled: snapshotEnabled } });
|
|
1528
|
+
const stateRef = React.useRef({
|
|
1529
|
+
client,
|
|
1530
|
+
queryKey,
|
|
1531
|
+
lease: null,
|
|
1532
|
+
hasInitialData: initialData !== undefined,
|
|
1533
|
+
fallback: {
|
|
1534
|
+
subscriptionId: `pending:${queryKey}`,
|
|
1535
|
+
query,
|
|
1536
|
+
keys: [],
|
|
1537
|
+
data: initialData ?? [],
|
|
1538
|
+
isLoading: enabled && snapshotEnabled && initialData === undefined,
|
|
1539
|
+
isRefreshing: false,
|
|
1540
|
+
},
|
|
1541
|
+
});
|
|
1542
|
+
if (stateRef.current.client !== client || stateRef.current.queryKey !== queryKey) {
|
|
1543
|
+
stateRef.current = {
|
|
1544
|
+
client,
|
|
1545
|
+
queryKey,
|
|
1546
|
+
lease: null,
|
|
1547
|
+
hasInitialData: initialData !== undefined,
|
|
1548
|
+
fallback: {
|
|
1549
|
+
subscriptionId: `pending:${queryKey}`,
|
|
1550
|
+
query,
|
|
1551
|
+
keys: [],
|
|
1552
|
+
data: initialData ?? [],
|
|
1553
|
+
isLoading: enabled && snapshotEnabled && initialData === undefined,
|
|
1554
|
+
isRefreshing: false,
|
|
1555
|
+
},
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
const state = stateRef.current;
|
|
1559
|
+
const subscribe = React.useCallback((callback) => {
|
|
1560
|
+
if (!enabled || !client)
|
|
1561
|
+
return noop;
|
|
1562
|
+
try {
|
|
1563
|
+
const lease = client.getSubscriptionRegistry().subscribe(query, snapshotEnabled);
|
|
1564
|
+
state.lease = lease;
|
|
1565
|
+
const unsubscribe = lease.onChange(callback);
|
|
1566
|
+
return () => {
|
|
1567
|
+
unsubscribe();
|
|
1568
|
+
lease.release();
|
|
1569
|
+
if (state.lease === lease)
|
|
1570
|
+
state.lease = null;
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
catch (value) {
|
|
1574
|
+
state.fallback = {
|
|
1575
|
+
...state.fallback,
|
|
1576
|
+
isLoading: false,
|
|
1577
|
+
isRefreshing: false,
|
|
1578
|
+
error: queryError(value, 'Subscription failed'),
|
|
1579
|
+
};
|
|
1580
|
+
return noop;
|
|
1581
|
+
}
|
|
1582
|
+
// queryKey represents every query field; state is replaced with that key.
|
|
1583
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1584
|
+
}, [client, enabled, queryKey, snapshotEnabled]);
|
|
1585
|
+
const getSnapshot = React.useCallback(() => {
|
|
1586
|
+
if (!state.lease)
|
|
1587
|
+
return state.fallback;
|
|
1588
|
+
try {
|
|
1589
|
+
const current = state.lease.getSnapshot();
|
|
1590
|
+
if (state.hasInitialData && current.isLoading && current.data.length === 0) {
|
|
1591
|
+
if (state.seededSource !== current) {
|
|
1592
|
+
state.seededSource = current;
|
|
1593
|
+
state.seededSnapshot = {
|
|
1594
|
+
...current,
|
|
1595
|
+
data: state.fallback.data,
|
|
1596
|
+
isLoading: false,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
return state.seededSnapshot;
|
|
1600
|
+
}
|
|
1601
|
+
return current;
|
|
1602
|
+
}
|
|
1603
|
+
catch (value) {
|
|
1604
|
+
state.fallback = {
|
|
1605
|
+
...state.fallback,
|
|
1606
|
+
isLoading: false,
|
|
1607
|
+
isRefreshing: false,
|
|
1608
|
+
error: queryError(value, 'Subscription snapshot failed'),
|
|
1609
|
+
};
|
|
1610
|
+
return state.fallback;
|
|
1611
|
+
}
|
|
1612
|
+
// state is replaced whenever either listed identity changes.
|
|
1613
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1614
|
+
}, [queryKey, client]);
|
|
1615
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1616
|
+
const getServerSnapshot = React.useCallback(() => state.fallback, [queryKey, client]);
|
|
1617
|
+
const snapshot = React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
1618
|
+
const refresh = React.useCallback(async () => {
|
|
1619
|
+
if (!state.lease)
|
|
1620
|
+
return;
|
|
1621
|
+
await state.lease.refresh();
|
|
1622
|
+
// state is replaced whenever either listed identity changes.
|
|
1623
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1624
|
+
}, [queryKey, client]);
|
|
1625
|
+
return { snapshot, refresh };
|
|
1626
|
+
}
|
|
1627
|
+
function computeStatus(enabled, client, snapshot) {
|
|
1628
|
+
if (!enabled)
|
|
1629
|
+
return 'disabled';
|
|
1630
|
+
if (!client)
|
|
1631
|
+
return 'connecting';
|
|
1632
|
+
if (snapshot.error)
|
|
1633
|
+
return 'error';
|
|
1634
|
+
return snapshot.isLoading ? 'subscribing' : 'ready';
|
|
1635
|
+
}
|
|
1636
|
+
function useProjectedData(snapshot, schema, onSchemaValidationError) {
|
|
1637
|
+
const callbackRef = React.useRef(onSchemaValidationError);
|
|
1638
|
+
callbackRef.current = onSchemaValidationError;
|
|
1639
|
+
const cache = React.useRef({ snapshot: null, schema, value: [], diagnostics: [] });
|
|
1640
|
+
if (cache.current.snapshot !== snapshot || cache.current.schema !== schema) {
|
|
1641
|
+
const value = [];
|
|
1642
|
+
const diagnostics = [];
|
|
1643
|
+
snapshot.data.forEach((entity, index) => {
|
|
1644
|
+
if (!schema) {
|
|
1645
|
+
value.push(entity);
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
try {
|
|
1649
|
+
const parsed = schema.safeParse(entity);
|
|
1650
|
+
if (parsed.success) {
|
|
1651
|
+
value.push(parsed.data);
|
|
1652
|
+
}
|
|
1653
|
+
else {
|
|
1654
|
+
diagnostics.push({
|
|
1655
|
+
view: snapshot.query.view,
|
|
1656
|
+
...(snapshot.keys[index] === undefined ? {} : { key: snapshot.keys[index] }),
|
|
1657
|
+
entity,
|
|
1658
|
+
error: parsed.error,
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
catch (error) {
|
|
1663
|
+
diagnostics.push({
|
|
1664
|
+
view: snapshot.query.view,
|
|
1665
|
+
...(snapshot.keys[index] === undefined ? {} : { key: snapshot.keys[index] }),
|
|
1666
|
+
entity,
|
|
1667
|
+
error,
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
cache.current = { snapshot, schema, value, diagnostics };
|
|
1672
|
+
}
|
|
1673
|
+
const projection = cache.current;
|
|
1674
|
+
React.useEffect(() => {
|
|
1675
|
+
const callback = callbackRef.current;
|
|
1676
|
+
if (!callback)
|
|
1677
|
+
return;
|
|
1678
|
+
for (const diagnostic of projection.diagnostics) {
|
|
1679
|
+
try {
|
|
1680
|
+
callback(diagnostic);
|
|
1681
|
+
}
|
|
1682
|
+
catch (error) {
|
|
1683
|
+
console.error('[Arete] View schema validation callback failed:', error);
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
}, [projection]);
|
|
1687
|
+
return projection.value;
|
|
1688
|
+
}
|
|
1689
|
+
function useStateView(viewDef, client, key, options) {
|
|
1690
|
+
const enabled = options?.enabled !== false && key !== null && key !== undefined;
|
|
1691
|
+
const keyString = key === null || key === undefined
|
|
1692
|
+
? undefined
|
|
1693
|
+
: sdk.serializeViewKey(viewDef, key);
|
|
1694
|
+
const query = {
|
|
1695
|
+
view: viewDef.view,
|
|
1696
|
+
...(keyString === undefined ? {} : { key: keyString }),
|
|
1697
|
+
...(options?.partition === undefined ? {} : { partition: options.partition }),
|
|
1698
|
+
...(options?.filters === undefined ? {} : { filters: options.filters }),
|
|
1699
|
+
...(options?.after === undefined ? {} : { after: options.after }),
|
|
1700
|
+
...(options?.snapshotLimit === undefined ? {} : { snapshotLimit: options.snapshotLimit }),
|
|
1701
|
+
};
|
|
1702
|
+
const snapshotEnabled = options?.withSnapshot ?? true;
|
|
1703
|
+
const { snapshot, refresh } = useQuery(client, query, snapshotEnabled, enabled, options?.initialData === undefined ? undefined : [options.initialData]);
|
|
1704
|
+
const data = useProjectedData(snapshot, options?.schema, options?.onSchemaValidationError)[0];
|
|
1705
|
+
const status = computeStatus(enabled, client, snapshot);
|
|
1706
|
+
return {
|
|
1707
|
+
data: enabled ? data : undefined,
|
|
1708
|
+
status,
|
|
1709
|
+
isPending: status === 'connecting' || status === 'subscribing',
|
|
1710
|
+
isReady: status === 'ready',
|
|
1711
|
+
isEmpty: status === 'ready' && data === undefined,
|
|
1712
|
+
isLoading: enabled && snapshot.isLoading,
|
|
1713
|
+
isRefreshing: enabled && snapshot.isRefreshing,
|
|
1714
|
+
error: status === 'error' ? snapshot.error : undefined,
|
|
1715
|
+
refresh,
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
function useListView(viewDef, client, params, options) {
|
|
1719
|
+
const enabled = options?.enabled !== false;
|
|
1720
|
+
const partition = params?.partition ?? options?.partition;
|
|
1721
|
+
const filters = params?.filters ?? options?.filters;
|
|
1722
|
+
const after = params?.after ?? options?.after;
|
|
1723
|
+
const snapshotLimit = params?.snapshotLimit ?? options?.snapshotLimit;
|
|
1724
|
+
const query = {
|
|
1725
|
+
view: viewDef.view,
|
|
1726
|
+
...(params?.key === undefined ? {} : { key: params.key }),
|
|
1727
|
+
...(partition === undefined ? {} : { partition }),
|
|
1728
|
+
...(filters === undefined ? {} : { filters }),
|
|
1729
|
+
...(params?.take === undefined ? {} : { take: params.take }),
|
|
1730
|
+
...(params?.skip === undefined ? {} : { skip: params.skip }),
|
|
1731
|
+
...(after === undefined ? {} : { after }),
|
|
1732
|
+
...(snapshotLimit === undefined ? {} : { snapshotLimit }),
|
|
1733
|
+
};
|
|
1734
|
+
const snapshotEnabled = params?.withSnapshot ?? options?.withSnapshot ?? true;
|
|
1735
|
+
const { snapshot, refresh } = useQuery(client, query, snapshotEnabled, enabled, options?.initialData);
|
|
1736
|
+
const data = useProjectedData(snapshot, (params?.schema ?? options?.schema), params?.onSchemaValidationError ?? options?.onSchemaValidationError);
|
|
1737
|
+
const status = computeStatus(enabled, client, snapshot);
|
|
1738
|
+
return {
|
|
1739
|
+
data: enabled ? data : options?.initialData,
|
|
1740
|
+
status,
|
|
1741
|
+
isPending: status === 'connecting' || status === 'subscribing',
|
|
1742
|
+
isReady: status === 'ready',
|
|
1743
|
+
isEmpty: status === 'ready' && data.length === 0,
|
|
1744
|
+
isLoading: enabled && snapshot.isLoading,
|
|
1745
|
+
isRefreshing: enabled && snapshot.isRefreshing,
|
|
1746
|
+
error: status === 'error' ? snapshot.error : undefined,
|
|
1747
|
+
refresh,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
function createStateViewHook(viewDef, client) {
|
|
1751
|
+
function use(key, options) {
|
|
1752
|
+
return useStateView(viewDef, client, key, options);
|
|
1753
|
+
}
|
|
1754
|
+
function refresh(key) {
|
|
1755
|
+
if (!client)
|
|
1756
|
+
return Promise.resolve();
|
|
1757
|
+
const keyString = key === null || key === undefined
|
|
1758
|
+
? undefined
|
|
1759
|
+
: sdk.serializeViewKey(viewDef, key);
|
|
1760
|
+
return client.getSubscriptionRegistry().refreshView(viewDef.view, keyString);
|
|
1761
|
+
}
|
|
1762
|
+
return { use, refresh };
|
|
1763
|
+
}
|
|
1764
|
+
function createListViewHook(viewDef, client) {
|
|
1765
|
+
function use(params, options) {
|
|
1766
|
+
const listOptions = params?.take === 1
|
|
1767
|
+
? options
|
|
1768
|
+
? {
|
|
1769
|
+
...options,
|
|
1770
|
+
initialData: options.initialData === undefined
|
|
1771
|
+
? undefined
|
|
1772
|
+
: [options.initialData],
|
|
1773
|
+
}
|
|
1774
|
+
: undefined
|
|
1775
|
+
: options;
|
|
1776
|
+
const result = useListView(viewDef, client, params, listOptions);
|
|
1777
|
+
if (params?.take !== 1)
|
|
1778
|
+
return result;
|
|
1779
|
+
return { ...result, data: result.data?.[0] };
|
|
1780
|
+
}
|
|
1781
|
+
function useOne(params, options) {
|
|
1782
|
+
const listOptions = options
|
|
1783
|
+
? {
|
|
1784
|
+
...options,
|
|
1785
|
+
initialData: options.initialData === undefined
|
|
1786
|
+
? undefined
|
|
1787
|
+
: [options.initialData],
|
|
1788
|
+
}
|
|
1789
|
+
: undefined;
|
|
1790
|
+
const result = useListView(viewDef, client, { ...(params ?? {}), take: 1 }, listOptions);
|
|
1791
|
+
return { ...result, data: result.data?.[0] };
|
|
1792
|
+
}
|
|
1793
|
+
function refresh() {
|
|
1794
|
+
if (!client)
|
|
1795
|
+
return Promise.resolve();
|
|
1796
|
+
return client.getSubscriptionRegistry().refreshView(viewDef.view);
|
|
1797
|
+
}
|
|
1798
|
+
return { use, useOne, refresh };
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
function normalizeConnectionError(value, fallback) {
|
|
1802
|
+
if (value instanceof Error)
|
|
1803
|
+
return value;
|
|
1804
|
+
return new Error(typeof value === 'string' && value.length > 0 ? value : fallback);
|
|
1805
|
+
}
|
|
1806
|
+
function useArete(stack,
|
|
1807
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1808
|
+
options
|
|
1809
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1810
|
+
) {
|
|
1811
|
+
const { getOrCreateClient, getClient, retryClient, subscribeToClientChanges, config, } = useAreteContext();
|
|
1812
|
+
const resolvedStack = stack ?? config.stack;
|
|
1813
|
+
if (!resolvedStack) {
|
|
1814
|
+
throw new Error('useArete() was called without a stack and no default stack is configured. ' +
|
|
1815
|
+
'Pass a stack explicitly or set <AreteProvider stack={...}>.');
|
|
1816
|
+
}
|
|
1817
|
+
const usesProviderStack = stack === undefined || stack === config.stack;
|
|
1818
|
+
const resolvedOptions = (usesProviderStack
|
|
1819
|
+
? { ...(config.stackOptions ?? {}), ...(options ?? {}) }
|
|
1820
|
+
: options);
|
|
1821
|
+
const url = resolvedOptions?.url;
|
|
1822
|
+
const httpUrl = resolvedOptions?.httpUrl;
|
|
1823
|
+
const transport = resolvedOptions?.transport;
|
|
1824
|
+
const attachedPrograms = resolvedOptions?.programs;
|
|
1825
|
+
const lookupOptions = React.useMemo(() => ({ url, httpUrl, transport, programs: attachedPrograms }), [url, httpUrl, transport, attachedPrograms]);
|
|
1826
|
+
const lookupKey = createClientCacheKey(resolvedStack, lookupOptions);
|
|
1827
|
+
const initialClient = getClient(resolvedStack, lookupOptions);
|
|
1828
|
+
const [clientState, setClientState] = React.useState(() => ({ lookupKey, client: initialClient }));
|
|
1829
|
+
const clientStateRef = React.useRef(clientState);
|
|
1830
|
+
clientStateRef.current = clientState;
|
|
1831
|
+
const client = clientState.lookupKey === lookupKey ? clientState.client : null;
|
|
1832
|
+
const [isConnecting, setIsConnecting] = React.useState(!initialClient);
|
|
1833
|
+
const [error, setError] = React.useState(null);
|
|
1834
|
+
const [socketIssue, setSocketIssue] = React.useState(null);
|
|
1835
|
+
const [connectionState, setConnectionState] = React.useState(() => client?.connectionState ?? 'disconnected');
|
|
1836
|
+
const retryPromiseRef = React.useRef(null);
|
|
1837
|
+
const lookupKeyRef = React.useRef(lookupKey);
|
|
1838
|
+
lookupKeyRef.current = lookupKey;
|
|
1839
|
+
React.useEffect(() => {
|
|
1840
|
+
let active = true;
|
|
1841
|
+
const existingClient = getClient(resolvedStack, lookupOptions);
|
|
1842
|
+
if (existingClient) {
|
|
1843
|
+
setClientState({
|
|
1844
|
+
lookupKey,
|
|
1845
|
+
client: existingClient,
|
|
1846
|
+
});
|
|
1847
|
+
setIsConnecting(false);
|
|
1848
|
+
setError(null);
|
|
1849
|
+
setSocketIssue(null);
|
|
1850
|
+
return () => {
|
|
1851
|
+
active = false;
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
setClientState({ lookupKey, client: null });
|
|
1855
|
+
setIsConnecting(true);
|
|
1856
|
+
setError(null);
|
|
1857
|
+
setSocketIssue(null);
|
|
1858
|
+
getOrCreateClient(resolvedStack, lookupOptions)
|
|
1859
|
+
.then((newClient) => {
|
|
1860
|
+
if (active) {
|
|
1861
|
+
setClientState({
|
|
1862
|
+
lookupKey,
|
|
1863
|
+
client: newClient,
|
|
1864
|
+
});
|
|
1865
|
+
setIsConnecting(false);
|
|
1866
|
+
}
|
|
1867
|
+
})
|
|
1868
|
+
.catch((err) => {
|
|
1869
|
+
if (active) {
|
|
1870
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1871
|
+
setIsConnecting(false);
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
return () => {
|
|
1875
|
+
active = false;
|
|
1876
|
+
};
|
|
1877
|
+
}, [resolvedStack, getOrCreateClient, getClient, lookupKey, lookupOptions]);
|
|
1878
|
+
React.useEffect(() => subscribeToClientChanges((change) => {
|
|
1879
|
+
if (change && change.cacheKey !== lookupKey)
|
|
1880
|
+
return;
|
|
1881
|
+
const sharedClient = getClient(resolvedStack, lookupOptions);
|
|
1882
|
+
const current = clientStateRef.current;
|
|
1883
|
+
if (change?.status === 'connecting') {
|
|
1884
|
+
const next = { lookupKey, client: null };
|
|
1885
|
+
clientStateRef.current = next;
|
|
1886
|
+
setClientState(next);
|
|
1887
|
+
setIsConnecting(true);
|
|
1888
|
+
setError(null);
|
|
1889
|
+
setSocketIssue(null);
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (change?.status === 'error') {
|
|
1893
|
+
const next = { lookupKey, client: null };
|
|
1894
|
+
clientStateRef.current = next;
|
|
1895
|
+
setClientState(next);
|
|
1896
|
+
setIsConnecting(false);
|
|
1897
|
+
setError(change.error ?? new Error('Arete connection attempt failed'));
|
|
1898
|
+
setSocketIssue(null);
|
|
1899
|
+
return;
|
|
1900
|
+
}
|
|
1901
|
+
if (current.lookupKey === lookupKey && current.client === sharedClient)
|
|
1902
|
+
return;
|
|
1903
|
+
const next = { lookupKey, client: sharedClient };
|
|
1904
|
+
clientStateRef.current = next;
|
|
1905
|
+
setClientState(next);
|
|
1906
|
+
setIsConnecting(sharedClient === null);
|
|
1907
|
+
setError(null);
|
|
1908
|
+
setSocketIssue(null);
|
|
1909
|
+
}), [getClient, lookupKey, lookupOptions, resolvedStack, subscribeToClientChanges]);
|
|
1910
|
+
React.useEffect(() => {
|
|
1911
|
+
if (!client) {
|
|
1912
|
+
setConnectionState('disconnected');
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
setConnectionState(client.connectionState);
|
|
1916
|
+
const unsubscribeState = client.onConnectionStateChange((state, stateError) => {
|
|
1917
|
+
setConnectionState(state);
|
|
1918
|
+
if (state === 'error') {
|
|
1919
|
+
setError(normalizeConnectionError(stateError, 'Arete connection entered an error state'));
|
|
1920
|
+
}
|
|
1921
|
+
else if (state === 'connected') {
|
|
1922
|
+
setError(null);
|
|
1923
|
+
setSocketIssue(null);
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
const unsubscribeIssue = client.onSocketIssue((issue) => {
|
|
1927
|
+
setSocketIssue(issue);
|
|
1928
|
+
});
|
|
1929
|
+
return () => {
|
|
1930
|
+
unsubscribeIssue();
|
|
1931
|
+
unsubscribeState();
|
|
1932
|
+
};
|
|
1933
|
+
}, [client]);
|
|
1934
|
+
const retry = React.useCallback(() => {
|
|
1935
|
+
if (retryPromiseRef.current?.lookupKey === lookupKey) {
|
|
1936
|
+
return retryPromiseRef.current.promise;
|
|
1937
|
+
}
|
|
1938
|
+
const retryLookupKey = lookupKey;
|
|
1939
|
+
setError(null);
|
|
1940
|
+
setSocketIssue(null);
|
|
1941
|
+
setIsConnecting(true);
|
|
1942
|
+
setClientState({ lookupKey: retryLookupKey, client: null });
|
|
1943
|
+
const attempt = retryClient(resolvedStack, lookupOptions).then((newClient) => {
|
|
1944
|
+
if (lookupKeyRef.current === retryLookupKey) {
|
|
1945
|
+
setClientState({
|
|
1946
|
+
lookupKey: retryLookupKey,
|
|
1947
|
+
client: newClient,
|
|
1948
|
+
});
|
|
1949
|
+
setIsConnecting(false);
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
const tracked = attempt.catch((value) => {
|
|
1953
|
+
const retryError = normalizeConnectionError(value, 'Arete connection retry failed');
|
|
1954
|
+
if (lookupKeyRef.current === retryLookupKey) {
|
|
1955
|
+
setError(retryError);
|
|
1956
|
+
setIsConnecting(false);
|
|
1957
|
+
}
|
|
1958
|
+
throw retryError;
|
|
1959
|
+
}).finally(() => {
|
|
1960
|
+
if (retryPromiseRef.current?.promise === tracked) {
|
|
1961
|
+
retryPromiseRef.current = null;
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
retryPromiseRef.current = { lookupKey, promise: tracked };
|
|
1965
|
+
return tracked;
|
|
1966
|
+
}, [lookupKey, lookupOptions, retryClient, resolvedStack]);
|
|
1967
|
+
const views = React.useMemo(() => {
|
|
1968
|
+
const result = {};
|
|
1969
|
+
for (const [viewName, viewGroup] of Object.entries(resolvedStack.views)) {
|
|
1970
|
+
result[viewName] = {};
|
|
1971
|
+
if (typeof viewGroup === 'object' && viewGroup !== null) {
|
|
1972
|
+
for (const [subViewName, viewDef] of Object.entries(viewGroup)) {
|
|
1973
|
+
if (!viewDef || typeof viewDef !== 'object' || !('mode' in viewDef))
|
|
1974
|
+
continue;
|
|
1975
|
+
if (viewDef.mode === 'state') {
|
|
1976
|
+
result[viewName][subViewName] = createStateViewHook(viewDef, client);
|
|
1977
|
+
}
|
|
1978
|
+
else if (viewDef.mode === 'list') {
|
|
1979
|
+
result[viewName][subViewName] = createListViewHook(viewDef, client);
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
return result;
|
|
1985
|
+
}, [resolvedStack, client]);
|
|
1986
|
+
const programs = React.useMemo(() => {
|
|
1987
|
+
if (!client) {
|
|
1988
|
+
// Placeholder hooks so components can render (and keep hook order)
|
|
1989
|
+
// before the client connects; submitting throws "not connected".
|
|
1990
|
+
return buildDisconnectedProgramHooks();
|
|
1991
|
+
}
|
|
1992
|
+
return buildProgramHookInterfaces(client.programs, client, useInstructionMutation, { defaultReconciliation: transport !== 'http' });
|
|
1993
|
+
}, [client, transport]);
|
|
1994
|
+
const connectedRead = (client?.read ?? null);
|
|
1995
|
+
const readArgCounts = sdk.getStackRuntimeExtensions(resolvedStack)?.readArgCounts;
|
|
1996
|
+
const read = React.useMemo(() => buildReadInterfaces(connectedRead, readArgCounts), [connectedRead, readArgCounts]);
|
|
1997
|
+
const isClientPending = clientState.lookupKey !== lookupKey || isConnecting;
|
|
1998
|
+
const visibleError = clientState.lookupKey === lookupKey ? error : null;
|
|
1999
|
+
const status = isClientPending
|
|
2000
|
+
? 'connecting'
|
|
2001
|
+
: visibleError
|
|
2002
|
+
? 'error'
|
|
2003
|
+
: client
|
|
2004
|
+
? connectionState
|
|
2005
|
+
: 'disconnected';
|
|
2006
|
+
return {
|
|
2007
|
+
views: views,
|
|
2008
|
+
queries: (client?.queries ?? {}),
|
|
2009
|
+
programs: programs,
|
|
2010
|
+
chain: client?.chain ?? null,
|
|
2011
|
+
zustandStore: client?.store?.store ?? null,
|
|
2012
|
+
client,
|
|
2013
|
+
read,
|
|
2014
|
+
reads: read,
|
|
2015
|
+
addresses: (client?.addresses
|
|
2016
|
+
?? resolvedStack.addresses),
|
|
2017
|
+
constants: (client?.constants
|
|
2018
|
+
?? resolvedStack.constants),
|
|
2019
|
+
defaults: (client?.defaults
|
|
2020
|
+
?? resolvedStack.defaults),
|
|
2021
|
+
math: (client?.math
|
|
2022
|
+
?? resolvedStack.math),
|
|
2023
|
+
connectionState: client ? connectionState : 'disconnected',
|
|
2024
|
+
status,
|
|
2025
|
+
isConnected: Boolean(client) && connectionState === 'connected',
|
|
2026
|
+
isLoading: isClientPending,
|
|
2027
|
+
canRetry: config.autoConnect !== false
|
|
2028
|
+
&& (status === 'error' || status === 'disconnected'),
|
|
2029
|
+
error: visibleError,
|
|
2030
|
+
socketIssue: clientState.lookupKey === lookupKey ? socketIssue : null,
|
|
2031
|
+
retry,
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
/**
|
|
2036
|
+
* Bind the React SDK to one generated stack without ambient module
|
|
2037
|
+
* augmentation. The returned hook remains explicit through an app-specific
|
|
2038
|
+
* name such as `useOre`, while the generic provider and hook remain available
|
|
2039
|
+
* for multi-stack applications.
|
|
2040
|
+
*/
|
|
2041
|
+
function createAreteReact(stack) {
|
|
2042
|
+
function Provider({ children, ...config }) {
|
|
2043
|
+
return (React.createElement(AreteProvider, { ...config, stack: stack }, children));
|
|
2044
|
+
}
|
|
2045
|
+
function useBoundArete(options) {
|
|
2046
|
+
return useArete(stack, options);
|
|
2047
|
+
}
|
|
2048
|
+
return {
|
|
2049
|
+
Provider,
|
|
2050
|
+
useArete: useBoundArete,
|
|
2051
|
+
stack,
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* Aggregate the status of connection, view, and read hook results into loading,
|
|
2057
|
+
* refreshing, and error lists. Pure function; call it during render.
|
|
2058
|
+
*
|
|
2059
|
+
* ```tsx
|
|
2060
|
+
* const streams = summarizeStatuses({ Board: board, Round: round, Miner: authority && miner });
|
|
2061
|
+
* {streams.loading.length > 0 && <p>Loading: {streams.loading.join(', ')}</p>}
|
|
2062
|
+
* {streams.errors.length > 0 && <p role="alert">{streams.errors.join(' · ')}</p>}
|
|
2063
|
+
* ```
|
|
2064
|
+
*/
|
|
2065
|
+
function summarizeStatuses(sources) {
|
|
2066
|
+
const loading = [];
|
|
2067
|
+
const errors = [];
|
|
2068
|
+
const refreshing = [];
|
|
2069
|
+
for (const [name, source] of Object.entries(sources)) {
|
|
2070
|
+
if (!source)
|
|
2071
|
+
continue;
|
|
2072
|
+
if (source.isLoading)
|
|
2073
|
+
loading.push(name);
|
|
2074
|
+
if (source.isRefreshing)
|
|
2075
|
+
refreshing.push(name);
|
|
2076
|
+
if (source.error)
|
|
2077
|
+
errors.push(`${name}: ${source.error.message}`);
|
|
2078
|
+
}
|
|
2079
|
+
return {
|
|
2080
|
+
isLoading: loading.length > 0,
|
|
2081
|
+
hasError: errors.length > 0,
|
|
2082
|
+
isRefreshing: refreshing.length > 0,
|
|
2083
|
+
loading,
|
|
2084
|
+
errors,
|
|
2085
|
+
refreshing,
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
/** @deprecated Use summarizeStatuses; it also accepts connection and read results. */
|
|
2089
|
+
const summarizeViews = summarizeStatuses;
|
|
2090
|
+
|
|
2091
|
+
const chainIds = new WeakMap();
|
|
2092
|
+
let nextChainId = 1;
|
|
2093
|
+
function chainKey(chain) {
|
|
2094
|
+
if (!chain) {
|
|
2095
|
+
return null;
|
|
2096
|
+
}
|
|
2097
|
+
const existing = chainIds.get(chain);
|
|
2098
|
+
if (existing !== undefined) {
|
|
2099
|
+
return existing;
|
|
2100
|
+
}
|
|
2101
|
+
const id = nextChainId++;
|
|
2102
|
+
chainIds.set(chain, id);
|
|
2103
|
+
return id;
|
|
2104
|
+
}
|
|
2105
|
+
function abortError() {
|
|
2106
|
+
const error = new Error('Chain read was aborted');
|
|
2107
|
+
error.name = 'AbortError';
|
|
2108
|
+
return error;
|
|
2109
|
+
}
|
|
2110
|
+
function useNativeBalance(chain, address, options = {}) {
|
|
2111
|
+
const enabled = (options.enabled ?? true) && Boolean(chain && address);
|
|
2112
|
+
const minContextSlot = options.minContextSlot;
|
|
2113
|
+
return useAsyncRead(['native-balance', chainKey(chain), address, minContextSlot?.toString()], async ({ signal }) => {
|
|
2114
|
+
if (!chain || !address) {
|
|
2115
|
+
throw new Error('Chain client and address are required for a native balance read');
|
|
2116
|
+
}
|
|
2117
|
+
if (signal.aborted)
|
|
2118
|
+
throw abortError();
|
|
2119
|
+
const result = await chain.nativeBalance(address, { minContextSlot });
|
|
2120
|
+
if (signal.aborted)
|
|
2121
|
+
throw abortError();
|
|
2122
|
+
return result;
|
|
2123
|
+
}, { enabled, initialData: options.initialData });
|
|
2124
|
+
}
|
|
2125
|
+
function useTokenBalance(chain, input, options = {}) {
|
|
2126
|
+
const enabled = (options.enabled ?? true) && Boolean(chain && input);
|
|
2127
|
+
const minContextSlot = options.minContextSlot;
|
|
2128
|
+
return useAsyncRead([
|
|
2129
|
+
'token-balance',
|
|
2130
|
+
chainKey(chain),
|
|
2131
|
+
input?.owner,
|
|
2132
|
+
input?.mint,
|
|
2133
|
+
input?.tokenProgram,
|
|
2134
|
+
minContextSlot?.toString(),
|
|
2135
|
+
], async ({ signal }) => {
|
|
2136
|
+
if (!chain || !input) {
|
|
2137
|
+
throw new Error('Chain client and token input are required for a token balance read');
|
|
2138
|
+
}
|
|
2139
|
+
if (signal.aborted)
|
|
2140
|
+
throw abortError();
|
|
2141
|
+
const result = await chain.balance(input, { minContextSlot });
|
|
2142
|
+
if (signal.aborted)
|
|
2143
|
+
throw abortError();
|
|
2144
|
+
return result;
|
|
2145
|
+
}, { enabled, initialData: options.initialData });
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
Object.defineProperty(exports, 'Arete', {
|
|
2149
|
+
enumerable: true,
|
|
2150
|
+
get: function () { return sdk.Arete; }
|
|
2151
|
+
});
|
|
2152
|
+
Object.defineProperty(exports, 'AreteError', {
|
|
2153
|
+
enumerable: true,
|
|
2154
|
+
get: function () { return sdk.AreteError; }
|
|
2155
|
+
});
|
|
2156
|
+
Object.defineProperty(exports, 'ConnectionManager', {
|
|
2157
|
+
enumerable: true,
|
|
2158
|
+
get: function () { return sdk.ConnectionManager; }
|
|
2159
|
+
});
|
|
2160
|
+
Object.defineProperty(exports, 'DEFAULT_CONFIG', {
|
|
2161
|
+
enumerable: true,
|
|
2162
|
+
get: function () { return sdk.DEFAULT_CONFIG; }
|
|
2163
|
+
});
|
|
2164
|
+
Object.defineProperty(exports, 'DEFAULT_MAX_ENTRIES_PER_VIEW', {
|
|
2165
|
+
enumerable: true,
|
|
2166
|
+
get: function () { return sdk.DEFAULT_MAX_ENTRIES_PER_VIEW; }
|
|
2167
|
+
});
|
|
2168
|
+
Object.defineProperty(exports, 'FrameProcessor', {
|
|
2169
|
+
enumerable: true,
|
|
2170
|
+
get: function () { return sdk.FrameProcessor; }
|
|
2171
|
+
});
|
|
2172
|
+
Object.defineProperty(exports, 'InstructionError', {
|
|
2173
|
+
enumerable: true,
|
|
2174
|
+
get: function () { return sdk.InstructionError; }
|
|
2175
|
+
});
|
|
2176
|
+
Object.defineProperty(exports, 'MemoryAdapter', {
|
|
2177
|
+
enumerable: true,
|
|
2178
|
+
get: function () { return sdk.MemoryAdapter; }
|
|
2179
|
+
});
|
|
2180
|
+
Object.defineProperty(exports, 'OperationCallbackError', {
|
|
2181
|
+
enumerable: true,
|
|
2182
|
+
get: function () { return sdk.OperationCallbackError; }
|
|
2183
|
+
});
|
|
2184
|
+
Object.defineProperty(exports, 'OperationExecutionError', {
|
|
2185
|
+
enumerable: true,
|
|
2186
|
+
get: function () { return sdk.OperationExecutionError; }
|
|
2187
|
+
});
|
|
2188
|
+
Object.defineProperty(exports, 'ProcessedSlotTimeoutError', {
|
|
2189
|
+
enumerable: true,
|
|
2190
|
+
get: function () { return sdk.ProcessedSlotTimeoutError; }
|
|
2191
|
+
});
|
|
2192
|
+
Object.defineProperty(exports, 'QueryStore', {
|
|
2193
|
+
enumerable: true,
|
|
2194
|
+
get: function () { return sdk.QueryStore; }
|
|
2195
|
+
});
|
|
2196
|
+
Object.defineProperty(exports, 'ReadRequestError', {
|
|
2197
|
+
enumerable: true,
|
|
2198
|
+
get: function () { return sdk.ReadRequestError; }
|
|
2199
|
+
});
|
|
2200
|
+
Object.defineProperty(exports, 'SubscriptionRegistry', {
|
|
2201
|
+
enumerable: true,
|
|
2202
|
+
get: function () { return sdk.SubscriptionRegistry; }
|
|
2203
|
+
});
|
|
2204
|
+
Object.defineProperty(exports, 'TransactionExecutionError', {
|
|
2205
|
+
enumerable: true,
|
|
2206
|
+
get: function () { return sdk.TransactionExecutionError; }
|
|
2207
|
+
});
|
|
2208
|
+
Object.defineProperty(exports, 'buildInstruction', {
|
|
2209
|
+
enumerable: true,
|
|
2210
|
+
get: function () { return sdk.buildInstruction; }
|
|
2211
|
+
});
|
|
2212
|
+
Object.defineProperty(exports, 'canonicalQueryKey', {
|
|
2213
|
+
enumerable: true,
|
|
2214
|
+
get: function () { return sdk.canonicalQueryKey; }
|
|
2215
|
+
});
|
|
2216
|
+
Object.defineProperty(exports, 'createInstructionExecutor', {
|
|
2217
|
+
enumerable: true,
|
|
2218
|
+
get: function () { return sdk.createInstructionExecutor; }
|
|
2219
|
+
});
|
|
2220
|
+
Object.defineProperty(exports, 'createInstructionHandler', {
|
|
2221
|
+
enumerable: true,
|
|
2222
|
+
get: function () { return sdk.createInstructionHandler; }
|
|
2223
|
+
});
|
|
2224
|
+
Object.defineProperty(exports, 'createPublicKeySeed', {
|
|
2225
|
+
enumerable: true,
|
|
2226
|
+
get: function () { return sdk.createPublicKeySeed; }
|
|
2227
|
+
});
|
|
2228
|
+
Object.defineProperty(exports, 'createSeed', {
|
|
2229
|
+
enumerable: true,
|
|
2230
|
+
get: function () { return sdk.createSeed; }
|
|
2231
|
+
});
|
|
2232
|
+
Object.defineProperty(exports, 'derivePda', {
|
|
2233
|
+
enumerable: true,
|
|
2234
|
+
get: function () { return sdk.derivePda; }
|
|
2235
|
+
});
|
|
2236
|
+
Object.defineProperty(exports, 'executeInstruction', {
|
|
2237
|
+
enumerable: true,
|
|
2238
|
+
get: function () { return sdk.executeInstruction; }
|
|
2239
|
+
});
|
|
2240
|
+
Object.defineProperty(exports, 'formatProgramError', {
|
|
2241
|
+
enumerable: true,
|
|
2242
|
+
get: function () { return sdk.formatProgramError; }
|
|
2243
|
+
});
|
|
2244
|
+
Object.defineProperty(exports, 'formatRawToUi', {
|
|
2245
|
+
enumerable: true,
|
|
2246
|
+
get: function () { return sdk.formatRawToUi; }
|
|
2247
|
+
});
|
|
2248
|
+
Object.defineProperty(exports, 'getTransactionFailureOutcome', {
|
|
2249
|
+
enumerable: true,
|
|
2250
|
+
get: function () { return sdk.getTransactionFailureOutcome; }
|
|
2251
|
+
});
|
|
2252
|
+
Object.defineProperty(exports, 'isErrorFrame', {
|
|
2253
|
+
enumerable: true,
|
|
2254
|
+
get: function () { return sdk.isErrorFrame; }
|
|
2255
|
+
});
|
|
2256
|
+
Object.defineProperty(exports, 'isHostedAreteEndpoint', {
|
|
2257
|
+
enumerable: true,
|
|
2258
|
+
get: function () { return sdk.isHostedAreteEndpoint; }
|
|
2259
|
+
});
|
|
2260
|
+
Object.defineProperty(exports, 'isSnapshotFrame', {
|
|
2261
|
+
enumerable: true,
|
|
2262
|
+
get: function () { return sdk.isSnapshotFrame; }
|
|
2263
|
+
});
|
|
2264
|
+
Object.defineProperty(exports, 'isSubscribedFrame', {
|
|
2265
|
+
enumerable: true,
|
|
2266
|
+
get: function () { return sdk.isSubscribedFrame; }
|
|
2267
|
+
});
|
|
2268
|
+
Object.defineProperty(exports, 'isUnsubscribedFrame', {
|
|
2269
|
+
enumerable: true,
|
|
2270
|
+
get: function () { return sdk.isUnsubscribedFrame; }
|
|
2271
|
+
});
|
|
2272
|
+
Object.defineProperty(exports, 'isValidFrame', {
|
|
2273
|
+
enumerable: true,
|
|
2274
|
+
get: function () { return sdk.isValidFrame; }
|
|
2275
|
+
});
|
|
2276
|
+
Object.defineProperty(exports, 'normalizeSubscriptionQuery', {
|
|
2277
|
+
enumerable: true,
|
|
2278
|
+
get: function () { return sdk.normalizeSubscriptionQuery; }
|
|
2279
|
+
});
|
|
2280
|
+
Object.defineProperty(exports, 'parseFrame', {
|
|
2281
|
+
enumerable: true,
|
|
2282
|
+
get: function () { return sdk.parseFrame; }
|
|
2283
|
+
});
|
|
2284
|
+
Object.defineProperty(exports, 'parseFrameFromBlob', {
|
|
2285
|
+
enumerable: true,
|
|
2286
|
+
get: function () { return sdk.parseFrameFromBlob; }
|
|
2287
|
+
});
|
|
2288
|
+
Object.defineProperty(exports, 'parseInstructionError', {
|
|
2289
|
+
enumerable: true,
|
|
2290
|
+
get: function () { return sdk.parseInstructionError; }
|
|
2291
|
+
});
|
|
2292
|
+
Object.defineProperty(exports, 'resolveAccounts', {
|
|
2293
|
+
enumerable: true,
|
|
2294
|
+
get: function () { return sdk.resolveAccounts; }
|
|
2295
|
+
});
|
|
2296
|
+
Object.defineProperty(exports, 'safeToRawAmount', {
|
|
2297
|
+
enumerable: true,
|
|
2298
|
+
get: function () { return sdk.safeToRawAmount; }
|
|
2299
|
+
});
|
|
2300
|
+
Object.defineProperty(exports, 'serializeInstructionData', {
|
|
2301
|
+
enumerable: true,
|
|
2302
|
+
get: function () { return sdk.serializeInstructionData; }
|
|
2303
|
+
});
|
|
2304
|
+
Object.defineProperty(exports, 'serializeViewKey', {
|
|
2305
|
+
enumerable: true,
|
|
2306
|
+
get: function () { return sdk.serializeViewKey; }
|
|
2307
|
+
});
|
|
2308
|
+
Object.defineProperty(exports, 'stringifyBigints', {
|
|
2309
|
+
enumerable: true,
|
|
2310
|
+
get: function () { return sdk.stringifyBigints; }
|
|
2311
|
+
});
|
|
2312
|
+
Object.defineProperty(exports, 'toRawAmount', {
|
|
2313
|
+
enumerable: true,
|
|
2314
|
+
get: function () { return sdk.toRawAmount; }
|
|
2315
|
+
});
|
|
2316
|
+
Object.defineProperty(exports, 'unwrapOperationExecutionError', {
|
|
2317
|
+
enumerable: true,
|
|
2318
|
+
get: function () { return sdk.unwrapOperationExecutionError; }
|
|
2319
|
+
});
|
|
2320
|
+
Object.defineProperty(exports, 'validateAccountResolution', {
|
|
2321
|
+
enumerable: true,
|
|
2322
|
+
get: function () { return sdk.validateAccountResolution; }
|
|
2323
|
+
});
|
|
2324
|
+
Object.defineProperty(exports, 'withPrograms', {
|
|
2325
|
+
enumerable: true,
|
|
2326
|
+
get: function () { return sdk.withPrograms; }
|
|
2327
|
+
});
|
|
2328
|
+
exports.AreteProvider = AreteProvider;
|
|
2329
|
+
exports.DEFAULT_RECONCILIATION_TIMEOUT_MS = DEFAULT_RECONCILIATION_TIMEOUT_MS;
|
|
2330
|
+
exports.ZustandAdapter = ZustandAdapter;
|
|
2331
|
+
exports.createAreteReact = createAreteReact;
|
|
2332
|
+
exports.createProcessedSlotReconciliation = createProcessedSlotReconciliation;
|
|
2333
|
+
exports.reconcileProcessedSlot = reconcileProcessedSlot;
|
|
2334
|
+
exports.summarizeStatuses = summarizeStatuses;
|
|
2335
|
+
exports.summarizeViews = summarizeViews;
|
|
2336
|
+
exports.useArete = useArete;
|
|
2337
|
+
exports.useAreteContext = useAreteContext;
|
|
2338
|
+
exports.useAsyncRead = useAsyncRead;
|
|
2339
|
+
exports.useConnectionState = useConnectionState;
|
|
2340
|
+
exports.useInstructionMutation = useInstructionMutation;
|
|
2341
|
+
exports.useNativeBalance = useNativeBalance;
|
|
2342
|
+
exports.useReconcileProcessedSlot = useReconcileProcessedSlot;
|
|
2343
|
+
exports.useTokenBalance = useTokenBalance;
|
|
2344
|
+
//# sourceMappingURL=index.cjs.map
|