@usearete/react 0.41.2 → 0.42.1

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 CHANGED
@@ -315,6 +315,46 @@ await or compose the result.
315
315
 
316
316
  Program and read hooks are safe to call while the client is still connecting: mutations throw "Arete client is not connected" if submitted early, and reads stay disabled.
317
317
 
318
+ ## Protocols
319
+
320
+ Protocol clients come from `@usearete/sdk` (`createProtocolClient(DEFINITION, { auth })` or `session.protocols.<key>`; see its README). Create one per key outside render (or memoize it) and pass it to these hooks:
321
+
322
+ ```tsx
323
+ import { createProtocolClient } from '@usearete/sdk';
324
+ import { useAllowedSteps, useProtocolEvents, useProtocolInstance } from '@usearete/react';
325
+ import TOKEN_RAISE from './generated/typescript/protocols/token-raise/token-raise';
326
+
327
+ const raise = createProtocolClient(TOKEN_RAISE, { auth: { publishableKey: 'a4_pk_...' } });
328
+
329
+ function Raise({ id, wallet }: { id: string; wallet?: string }) {
330
+ const { instance, live, error, isLoading } = useProtocolInstance(raise, id, { wallet });
331
+ const steps = useAllowedSteps(raise, { instance: id, wallet }, { refreshOnEvents: true });
332
+ useProtocolEvents(raise, id, {
333
+ onEvent: (event) => {
334
+ if (event.type === 'emit' && event.event === 'raise.succeeded') celebrate();
335
+ },
336
+ });
337
+
338
+ if (isLoading) return <Spinner />;
339
+ const contribute = steps.data?.find((step) => step.step === 'contribute');
340
+ return (
341
+ <button
342
+ disabled={!contribute?.allowed}
343
+ onClick={() => raise.steps.contribute.prepare({ instance: id, wallet: wallet!, amount: '1000000' })}
344
+ >
345
+ {instance?.state} {contribute?.reason}
346
+ </button>
347
+ );
348
+ }
349
+ ```
350
+
351
+ - `useProtocolInstance(client, instanceId, { wallet? })` returns the canonical instance, refreshed on every event of the instance's stream, plus `live` (the protocol stack's row when the client belongs to a session whose protocol ships a stack). The stream opens while mounted.
352
+ - `useAllowedSteps(client, { instance, wallet })` and `useProtocolParticipant(client, { instance, wallet })` are one-shot reads with the same `status`, `refresh()` and argument-keyed behavior as other read hooks; they stay disabled until every argument is set. `refreshOnEvents: true` re-reads on stream events.
353
+ - `useWalletActions(client, { wallet, instance?, cursor?, limit? })` reads `client.actions`: the create steps and one page of instances the wallet relates to, with each step's availability, reason and typed args. It is disabled until `wallet` is set and re-reads when any argument changes (pass `data.nextCursor` as `cursor` to page). With `instance` set, `refreshOnEvents: true` re-reads on that instance's stream events.
354
+ - `useProtocolEvents(client, instanceId, { onEvent, onError?, lastEventId? })` follows the stream with the latest callbacks, reopening only when the client or instance changes.
355
+
356
+ All of them keep the generated types: instance and participant states, step names, guard codes and event names. A publishable-key client has only the `public` steps, so a secret step does not compile in browser code.
357
+
318
358
  ## Safe amount parsing
319
359
 
320
360
  `safeToRawAmount(input, decimals)` is re-exported by `@usearete/react` for form validation. Unlike `toRawAmount`, it reports invalid input without throwing:
package/dist/index.cjs CHANGED
@@ -2186,7 +2186,7 @@ function chainKey(chain) {
2186
2186
  chainIds.set(chain, id);
2187
2187
  return id;
2188
2188
  }
2189
- function abortError() {
2189
+ function abortError$1() {
2190
2190
  const error = new Error('Chain read was aborted');
2191
2191
  error.name = 'AbortError';
2192
2192
  return error;
@@ -2199,10 +2199,10 @@ function useNativeBalance(chain, address, options = {}) {
2199
2199
  throw new Error('Chain client and address are required for a native balance read');
2200
2200
  }
2201
2201
  if (signal.aborted)
2202
- throw abortError();
2202
+ throw abortError$1();
2203
2203
  const result = await chain.nativeBalance(address, { minContextSlot });
2204
2204
  if (signal.aborted)
2205
- throw abortError();
2205
+ throw abortError$1();
2206
2206
  return result;
2207
2207
  }, { enabled, initialData: options.initialData });
2208
2208
  }
@@ -2221,14 +2221,172 @@ function useTokenBalance(chain, input, options = {}) {
2221
2221
  throw new Error('Chain client and token input are required for a token balance read');
2222
2222
  }
2223
2223
  if (signal.aborted)
2224
- throw abortError();
2224
+ throw abortError$1();
2225
2225
  const result = await chain.balance(input, { minContextSlot });
2226
2226
  if (signal.aborted)
2227
- throw abortError();
2227
+ throw abortError$1();
2228
2228
  return result;
2229
2229
  }, { enabled, initialData: options.initialData });
2230
2230
  }
2231
2231
 
2232
+ const clientIds = new WeakMap();
2233
+ let nextClientId = 1;
2234
+ function clientKey(client) {
2235
+ if (!client)
2236
+ return null;
2237
+ const existing = clientIds.get(client);
2238
+ if (existing !== undefined)
2239
+ return existing;
2240
+ const id = nextClientId++;
2241
+ clientIds.set(client, id);
2242
+ return id;
2243
+ }
2244
+ function abortError() {
2245
+ const error = new Error('Protocol read was aborted');
2246
+ error.name = 'AbortError';
2247
+ return error;
2248
+ }
2249
+ const EMPTY_VIEW = { snapshot: undefined, error: undefined };
2250
+ /**
2251
+ * Follow one protocol instance: the canonical instance from the hosted API,
2252
+ * refreshed whenever its event stream reports a change, plus the protocol
2253
+ * stack's live row when available. The stream opens while the component is
2254
+ * mounted and closes when it unmounts.
2255
+ */
2256
+ function useProtocolInstance(client, instanceId, options = {}) {
2257
+ const enabled = (options.enabled ?? true) && Boolean(client && instanceId);
2258
+ const wallet = options.wallet ?? undefined;
2259
+ const handle = React.useMemo(() => (enabled && client && instanceId
2260
+ ? client.instances.use(instanceId, wallet ? { wallet } : undefined)
2261
+ : undefined), [client, instanceId, wallet, enabled]);
2262
+ React.useEffect(() => () => handle?.close(), [handle]);
2263
+ const cache = React.useRef({ handle: undefined, view: EMPTY_VIEW });
2264
+ const subscribe = React.useCallback((listener) => (handle ? handle.subscribe(listener) : () => undefined), [handle]);
2265
+ const getSnapshot = React.useCallback(() => {
2266
+ if (!handle)
2267
+ return EMPTY_VIEW;
2268
+ const snapshot = handle.get();
2269
+ const error = handle.getError();
2270
+ const cached = cache.current;
2271
+ if (cached.handle === handle && cached.view.snapshot === snapshot && cached.view.error === error) {
2272
+ return cached.view;
2273
+ }
2274
+ const view = { snapshot, error };
2275
+ cache.current = { handle, view };
2276
+ return view;
2277
+ }, [handle]);
2278
+ const view = React.useSyncExternalStore(subscribe, getSnapshot, () => EMPTY_VIEW);
2279
+ const refresh = React.useCallback(async () => {
2280
+ await handle?.refresh();
2281
+ }, [handle]);
2282
+ return {
2283
+ instance: view.snapshot?.instance,
2284
+ live: view.snapshot?.live,
2285
+ error: view.error,
2286
+ isLoading: Boolean(handle) && view.snapshot === undefined && view.error === undefined,
2287
+ refresh,
2288
+ };
2289
+ }
2290
+ function useRefreshOnEvents(client, instanceId, enabled, refresh) {
2291
+ const refreshRef = React.useRef(refresh);
2292
+ refreshRef.current = refresh;
2293
+ React.useEffect(() => {
2294
+ if (!enabled || !client || !instanceId)
2295
+ return undefined;
2296
+ const subscription = client.events(instanceId, {
2297
+ onEvent: () => {
2298
+ void refreshRef.current().catch(() => undefined);
2299
+ },
2300
+ });
2301
+ return () => subscription.close();
2302
+ }, [client, instanceId, enabled]);
2303
+ }
2304
+ /**
2305
+ * Which steps `wallet` can call on `instance` now, and why not (a guard code
2306
+ * or an access code).
2307
+ */
2308
+ function useAllowedSteps(client, input, options = {}) {
2309
+ const { instance, wallet } = input;
2310
+ const enabled = (options.enabled ?? true) && Boolean(client && instance && wallet);
2311
+ const result = useAsyncRead(['protocol-allowed-steps', clientKey(client), instance, wallet], async ({ signal }) => {
2312
+ if (!client || !instance || !wallet) {
2313
+ throw new Error('A protocol client, instance and wallet are required for allowed steps');
2314
+ }
2315
+ if (signal.aborted)
2316
+ throw abortError();
2317
+ const steps = await client.allowedSteps({ instance, wallet, signal });
2318
+ if (signal.aborted)
2319
+ throw abortError();
2320
+ return steps;
2321
+ }, { enabled, initialData: options.initialData, debounceMs: options.debounceMs });
2322
+ useRefreshOnEvents(client, instance, enabled && (options.refreshOnEvents ?? false), result.refresh);
2323
+ return result;
2324
+ }
2325
+ /**
2326
+ * Which steps `wallet` can call now across the deployment: create steps and
2327
+ * one page of the instances it relates to, each with its availability,
2328
+ * reason and typed arguments. With `instance` set, `refreshOnEvents: true`
2329
+ * re-reads on that instance's stream events.
2330
+ */
2331
+ function useWalletActions(client, input, options = {}) {
2332
+ const { wallet } = input;
2333
+ const instance = input.instance ?? undefined;
2334
+ const cursor = input.cursor ?? undefined;
2335
+ const limit = input.limit;
2336
+ const enabled = (options.enabled ?? true) && Boolean(client && wallet);
2337
+ const result = useAsyncRead(['protocol-wallet-actions', clientKey(client), wallet, instance, cursor, limit], async ({ signal }) => {
2338
+ if (!client || !wallet) {
2339
+ throw new Error('A protocol client and wallet are required for wallet actions');
2340
+ }
2341
+ if (signal.aborted)
2342
+ throw abortError();
2343
+ const actions = await client.actions({ wallet, instance, cursor, limit, signal });
2344
+ if (signal.aborted)
2345
+ throw abortError();
2346
+ return actions;
2347
+ }, { enabled, initialData: options.initialData, debounceMs: options.debounceMs });
2348
+ useRefreshOnEvents(client, instance, enabled && (options.refreshOnEvents ?? false), result.refresh);
2349
+ return result;
2350
+ }
2351
+ /** One wallet's participant record on an instance. */
2352
+ function useProtocolParticipant(client, input, options = {}) {
2353
+ const { instance, wallet } = input;
2354
+ const enabled = (options.enabled ?? true) && Boolean(client && instance && wallet);
2355
+ const result = useAsyncRead(['protocol-participant', clientKey(client), instance, wallet], async ({ signal }) => {
2356
+ if (!client || !instance || !wallet) {
2357
+ throw new Error('A protocol client, instance and wallet are required for a participant read');
2358
+ }
2359
+ if (signal.aborted)
2360
+ throw abortError();
2361
+ const participant = await client.participants.get({ instance, wallet, signal });
2362
+ if (signal.aborted)
2363
+ throw abortError();
2364
+ return participant;
2365
+ }, { enabled, initialData: options.initialData, debounceMs: options.debounceMs });
2366
+ useRefreshOnEvents(client, instance, enabled && (options.refreshOnEvents ?? false), result.refresh);
2367
+ return result;
2368
+ }
2369
+ /**
2370
+ * Follow an instance's event stream while mounted. The latest callbacks are
2371
+ * always used; the stream reopens only when the client or instance changes.
2372
+ */
2373
+ function useProtocolEvents(client, instanceId, options) {
2374
+ const optionsRef = React.useRef(options);
2375
+ optionsRef.current = options;
2376
+ const enabled = (options.enabled ?? true) && Boolean(client && instanceId);
2377
+ React.useEffect(() => {
2378
+ if (!enabled || !client || !instanceId)
2379
+ return undefined;
2380
+ const eventOptions = {
2381
+ lastEventId: optionsRef.current.lastEventId,
2382
+ onEvent: (event) => optionsRef.current.onEvent(event),
2383
+ onError: (error) => optionsRef.current.onError?.(error),
2384
+ };
2385
+ const subscription = client.events(instanceId, eventOptions);
2386
+ return () => subscription.close();
2387
+ }, [client, instanceId, enabled]);
2388
+ }
2389
+
2232
2390
  Object.defineProperty(exports, 'Arete', {
2233
2391
  enumerable: true,
2234
2392
  get: function () { return sdk.Arete; }
@@ -2417,12 +2575,17 @@ exports.createProcessedSlotReconciliation = createProcessedSlotReconciliation;
2417
2575
  exports.reconcileProcessedSlot = reconcileProcessedSlot;
2418
2576
  exports.summarizeStatuses = summarizeStatuses;
2419
2577
  exports.summarizeViews = summarizeViews;
2578
+ exports.useAllowedSteps = useAllowedSteps;
2420
2579
  exports.useArete = useArete;
2421
2580
  exports.useAreteContext = useAreteContext;
2422
2581
  exports.useAsyncRead = useAsyncRead;
2423
2582
  exports.useConnectionState = useConnectionState;
2424
2583
  exports.useInstructionMutation = useInstructionMutation;
2425
2584
  exports.useNativeBalance = useNativeBalance;
2585
+ exports.useProtocolEvents = useProtocolEvents;
2586
+ exports.useProtocolInstance = useProtocolInstance;
2587
+ exports.useProtocolParticipant = useProtocolParticipant;
2426
2588
  exports.useReconcileProcessedSlot = useReconcileProcessedSlot;
2427
2589
  exports.useTokenBalance = useTokenBalance;
2590
+ exports.useWalletActions = useWalletActions;
2428
2591
  //# sourceMappingURL=index.cjs.map