@farcaster/snap 2.8.0 → 2.10.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.
Files changed (37) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/react/catalog-renderer.d.ts +5 -5
  4. package/dist/react/catalog-renderer.js +16 -4
  5. package/dist/react/components/action-button.js +23 -5
  6. package/dist/react/index.d.ts +2 -13
  7. package/dist/react/snap-view-core.js +90 -45
  8. package/dist/react/v1/snap-view.js +1 -1
  9. package/dist/react/v2/snap-view.js +1 -1
  10. package/dist/react-native/components/snap-action-button.js +6 -1
  11. package/dist/react-native/snap-view-core.js +77 -44
  12. package/dist/react-native/types.d.ts +2 -13
  13. package/dist/render-state.d.ts +9 -0
  14. package/dist/render-state.js +27 -0
  15. package/dist/schemas.d.ts +123 -3
  16. package/dist/schemas.js +53 -2
  17. package/dist/server/parseRequest.js +19 -3
  18. package/dist/ui/button.d.ts +1 -0
  19. package/dist/ui/button.js +1 -0
  20. package/dist/ui/catalog.d.ts +13 -14
  21. package/dist/ui/catalog.js +15 -22
  22. package/package.json +1 -1
  23. package/src/index.ts +7 -0
  24. package/src/react/catalog-renderer.tsx +57 -3
  25. package/src/react/components/action-button.tsx +32 -3
  26. package/src/react/index.tsx +4 -14
  27. package/src/react/snap-view-core.tsx +144 -48
  28. package/src/react/v1/snap-view.tsx +1 -0
  29. package/src/react/v2/snap-view.tsx +1 -0
  30. package/src/react-native/components/snap-action-button.tsx +6 -1
  31. package/src/react-native/snap-view-core.tsx +114 -48
  32. package/src/react-native/types.ts +4 -14
  33. package/src/render-state.ts +46 -0
  34. package/src/schemas.ts +73 -2
  35. package/src/server/parseRequest.ts +37 -6
  36. package/src/ui/button.ts +1 -0
  37. package/src/ui/catalog.ts +16 -25
@@ -136,6 +136,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
136
136
  "trending-up": "trending-up";
137
137
  "trending-down": "trending-down";
138
138
  }>>;
139
+ disabled: z.ZodOptional<z.ZodBoolean>;
139
140
  }, z.core.$strip>;
140
141
  description: string;
141
142
  };
@@ -509,48 +510,56 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
509
510
  submit: {
510
511
  description: string;
511
512
  params: z.ZodObject<{
513
+ activityKey: z.ZodOptional<z.ZodString>;
512
514
  target: z.ZodString;
513
515
  }, z.core.$strip>;
514
516
  };
515
517
  open_url: {
516
518
  description: string;
517
519
  params: z.ZodObject<{
520
+ activityKey: z.ZodOptional<z.ZodString>;
518
521
  target: z.ZodString;
519
522
  }, z.core.$strip>;
520
523
  };
521
524
  open_snap: {
522
525
  description: string;
523
526
  params: z.ZodObject<{
527
+ activityKey: z.ZodOptional<z.ZodString>;
524
528
  target: z.ZodString;
525
529
  }, z.core.$strip>;
526
530
  };
527
531
  open_mini_app: {
528
532
  description: string;
529
533
  params: z.ZodObject<{
534
+ activityKey: z.ZodOptional<z.ZodString>;
530
535
  target: z.ZodString;
531
536
  }, z.core.$strip>;
532
537
  };
533
538
  view_cast: {
534
539
  description: string;
535
540
  params: z.ZodObject<{
541
+ activityKey: z.ZodOptional<z.ZodString>;
536
542
  hash: z.ZodString;
537
543
  }, z.core.$strip>;
538
544
  };
539
545
  view_profile: {
540
546
  description: string;
541
547
  params: z.ZodObject<{
548
+ activityKey: z.ZodOptional<z.ZodString>;
542
549
  fid: z.ZodNumber;
543
550
  }, z.core.$strip>;
544
551
  };
545
552
  view_channel: {
546
553
  description: string;
547
554
  params: z.ZodObject<{
555
+ activityKey: z.ZodOptional<z.ZodString>;
548
556
  channelKey: z.ZodString;
549
557
  }, z.core.$strip>;
550
558
  };
551
559
  compose_cast: {
552
560
  description: string;
553
561
  params: z.ZodObject<{
562
+ activityKey: z.ZodOptional<z.ZodString>;
554
563
  text: z.ZodOptional<z.ZodString>;
555
564
  channelKey: z.ZodOptional<z.ZodString>;
556
565
  embeds: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -559,12 +568,14 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
559
568
  view_token: {
560
569
  description: string;
561
570
  params: z.ZodObject<{
571
+ activityKey: z.ZodOptional<z.ZodString>;
562
572
  token: z.ZodString;
563
573
  }, z.core.$strip>;
564
574
  };
565
575
  send_token: {
566
576
  description: string;
567
577
  params: z.ZodObject<{
578
+ activityKey: z.ZodOptional<z.ZodString>;
568
579
  token: z.ZodString;
569
580
  amount: z.ZodOptional<z.ZodString>;
570
581
  recipientFid: z.ZodOptional<z.ZodNumber>;
@@ -574,6 +585,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
574
585
  swap_token: {
575
586
  description: string;
576
587
  params: z.ZodObject<{
588
+ activityKey: z.ZodOptional<z.ZodString>;
577
589
  sellToken: z.ZodOptional<z.ZodString>;
578
590
  buyToken: z.ZodOptional<z.ZodString>;
579
591
  }, z.core.$strip>;
@@ -581,6 +593,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
581
593
  send_transaction: {
582
594
  description: string;
583
595
  params: z.ZodObject<{
596
+ activityKey: z.ZodOptional<z.ZodString>;
584
597
  chainId: z.ZodString;
585
598
  to: z.ZodString;
586
599
  data: z.ZodOptional<z.ZodString>;
@@ -591,20 +604,6 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
591
604
  maxPriorityFeePerGas: z.ZodOptional<z.ZodString>;
592
605
  }, z.core.$strip>;
593
606
  };
594
- send_calls: {
595
- description: string;
596
- params: z.ZodObject<{
597
- version: z.ZodOptional<z.ZodLiteral<"1.0">>;
598
- chainId: z.ZodString;
599
- atomicRequired: z.ZodOptional<z.ZodBoolean>;
600
- id: z.ZodOptional<z.ZodString>;
601
- calls: z.ZodArray<z.ZodObject<{
602
- to: z.ZodOptional<z.ZodString>;
603
- data: z.ZodOptional<z.ZodString>;
604
- value: z.ZodOptional<z.ZodString>;
605
- }, z.core.$strip>>;
606
- }, z.core.$strip>;
607
- };
608
607
  paginator_next: {
609
608
  description: string;
610
609
  params: z.ZodObject<{
@@ -21,6 +21,9 @@ import { cellGridProps } from "./cell-grid.js";
21
21
  const snapClientParams = z.object({
22
22
  client_action: z.record(z.string(), z.unknown()),
23
23
  });
24
+ const activityParams = {
25
+ activityKey: z.string().min(1).max(64).optional(),
26
+ };
24
27
  /**
25
28
  * json-render catalog for snap elements.
26
29
  *
@@ -101,31 +104,31 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
101
104
  actions: {
102
105
  submit: {
103
106
  description: "POST to snap server with signed body (fid, inputs, timestamp, signature); response is next snap page.",
104
- params: z.object({ target: z.string() }),
107
+ params: z.object({ target: z.string(), ...activityParams }),
105
108
  },
106
109
  open_url: {
107
110
  description: "Open external URL in browser.",
108
- params: z.object({ target: z.string() }),
111
+ params: z.object({ target: z.string(), ...activityParams }),
109
112
  },
110
113
  open_snap: {
111
114
  description: "Open a snap URL inline. The client renders the target as a snap rather than opening a browser.",
112
- params: z.object({ target: z.string() }),
115
+ params: z.object({ target: z.string(), ...activityParams }),
113
116
  },
114
117
  open_mini_app: {
115
118
  description: "Open target URL as a Farcaster mini app.",
116
- params: z.object({ target: z.string() }),
119
+ params: z.object({ target: z.string(), ...activityParams }),
117
120
  },
118
121
  view_cast: {
119
122
  description: "Navigate to a cast by hash.",
120
- params: z.object({ hash: z.string() }),
123
+ params: z.object({ hash: z.string(), ...activityParams }),
121
124
  },
122
125
  view_profile: {
123
126
  description: "Navigate to a user profile by FID.",
124
- params: z.object({ fid: z.number() }),
127
+ params: z.object({ fid: z.number(), ...activityParams }),
125
128
  },
126
129
  view_channel: {
127
130
  description: "Navigate to a Farcaster channel by channel key.",
128
- params: z.object({ channelKey: z.string() }),
131
+ params: z.object({ channelKey: z.string(), ...activityParams }),
129
132
  },
130
133
  compose_cast: {
131
134
  description: "Open the cast composer with optional pre-filled content.",
@@ -133,11 +136,12 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
133
136
  text: z.string().optional(),
134
137
  channelKey: z.string().optional(),
135
138
  embeds: z.array(z.string()).optional(),
139
+ ...activityParams,
136
140
  }),
137
141
  },
138
142
  view_token: {
139
143
  description: "View a token in the wallet. Token is a CAIP-19 identifier.",
140
- params: z.object({ token: z.string() }),
144
+ params: z.object({ token: z.string(), ...activityParams }),
141
145
  },
142
146
  send_token: {
143
147
  description: "Open send flow for a token. Token is CAIP-19.",
@@ -146,6 +150,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
146
150
  amount: z.string().optional(),
147
151
  recipientFid: z.number().optional(),
148
152
  recipientAddress: z.string().optional(),
153
+ ...activityParams,
149
154
  }),
150
155
  },
151
156
  swap_token: {
@@ -153,6 +158,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
153
158
  params: z.object({
154
159
  sellToken: z.string().optional(),
155
160
  buyToken: z.string().optional(),
161
+ ...activityParams,
156
162
  }),
157
163
  },
158
164
  send_transaction: {
@@ -166,20 +172,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
166
172
  gasPrice: z.string().optional(),
167
173
  maxFeePerGas: z.string().optional(),
168
174
  maxPriorityFeePerGas: z.string().optional(),
169
- }),
170
- },
171
- send_calls: {
172
- description: "Request one or more EVM calls through the host wallet using wallet_sendCalls.",
173
- params: z.object({
174
- version: z.literal("1.0").optional(),
175
- chainId: z.string(),
176
- atomicRequired: z.boolean().optional(),
177
- id: z.string().optional(),
178
- calls: z.array(z.object({
179
- to: z.string().optional(),
180
- data: z.string().optional(),
181
- value: z.string().optional(),
182
- })),
175
+ ...activityParams,
183
176
  }),
184
177
  },
185
178
  paginator_next: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
package/src/index.ts CHANGED
@@ -32,11 +32,15 @@ export {
32
32
  export {
33
33
  ACTION_TYPE_GET,
34
34
  ACTION_TYPE_POST,
35
+ ACTION_TYPE_TRANSACTION_RESULT,
35
36
  snapResponseSchema,
36
37
  payloadSchema,
37
38
  getPayloadSchema,
39
+ transactionResultPayloadSchema,
40
+ snapTransactionResultSchema,
38
41
  type SnapAction,
39
42
  type SnapGetAction,
43
+ type SnapTransactionResultAction,
40
44
  type SnapContext,
41
45
  type SnapResponse,
42
46
  type SnapHandlerResult,
@@ -45,6 +49,9 @@ export {
45
49
  type SnapFunction,
46
50
  type SnapPayload,
47
51
  type SnapGetPayload,
52
+ type SnapSendTransactionParams,
53
+ type SnapTransactionResult,
54
+ type SnapTransactionResultPayload,
48
55
  } from "./schemas";
49
56
  export { validateSnapResponse, type ValidationResult } from "./validator";
50
57
  export type { SnapRenderState } from "./render-state";
@@ -1,6 +1,12 @@
1
1
  "use client";
2
2
 
3
- import { createRenderer } from "@json-render/react";
3
+ import {
4
+ JSONUIProvider,
5
+ Renderer,
6
+ type ComponentRegistry,
7
+ type CreateRendererProps,
8
+ } from "@json-render/react";
9
+ import { useMemo, type ReactNode } from "react";
4
10
  import { snapJsonRenderCatalog } from "@farcaster/snap/ui";
5
11
  import { SnapActionButton } from "./components/action-button";
6
12
  import { SnapBadge } from "./components/badge";
@@ -24,7 +30,7 @@ import { SnapCellGrid } from "./components/cell-grid";
24
30
  * Maps snap json-render catalog types to React components.
25
31
  * Keys match the snap wire-format `type` strings exactly.
26
32
  */
27
- export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
33
+ const snapCatalogRegistry = {
28
34
  badge: SnapBadge,
29
35
  button: SnapActionButton,
30
36
  icon: SnapIcon,
@@ -42,4 +48,52 @@ export const SnapCatalogView = createRenderer(snapJsonRenderCatalog, {
42
48
  toggle_group: SnapToggleGroup,
43
49
  bar_chart: SnapBarChart,
44
50
  cell_grid: SnapCellGrid,
45
- });
51
+ } satisfies ComponentRegistry;
52
+
53
+ export function SnapCatalogView({
54
+ spec,
55
+ store,
56
+ state,
57
+ onAction,
58
+ onStateChange,
59
+ functions,
60
+ loading,
61
+ fallback,
62
+ children,
63
+ }: CreateRendererProps & { children?: ReactNode }) {
64
+ const actionHandlers = useMemo(
65
+ () =>
66
+ onAction
67
+ ? new Proxy<Record<string, (params: Record<string, unknown>) => unknown>>(
68
+ {},
69
+ {
70
+ get: (_target, prop) => {
71
+ return (params: Record<string, unknown>) =>
72
+ onAction(String(prop), params);
73
+ },
74
+ has: () => true,
75
+ },
76
+ )
77
+ : undefined,
78
+ [onAction],
79
+ );
80
+
81
+ return (
82
+ <JSONUIProvider
83
+ registry={snapCatalogRegistry}
84
+ store={store}
85
+ initialState={state}
86
+ handlers={actionHandlers}
87
+ functions={functions}
88
+ onStateChange={onStateChange}
89
+ >
90
+ <Renderer
91
+ spec={spec}
92
+ registry={snapCatalogRegistry}
93
+ loading={loading}
94
+ fallback={fallback}
95
+ />
96
+ {children}
97
+ </JSONUIProvider>
98
+ );
99
+ }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
4
- import { useStateStore } from "@json-render/react";
3
+ import { useMemo, useState } from "react";
4
+ import { useStateStore, useStateValue } from "@json-render/react";
5
5
  import { ExternalLink } from "lucide-react";
6
6
  import { Button } from "@neynar/ui/button";
7
7
  import { cn } from "@neynar/ui/utils";
@@ -10,6 +10,7 @@ import {
10
10
  getPaginatorAction,
11
11
  runPaginatorAction,
12
12
  } from "../../ui/paginator-state";
13
+ import { buildActionActivityStateChanges } from "../../render-state";
13
14
  import { useSnapStackDirection } from "../stack-direction-context";
14
15
  import { ICON_MAP } from "./icon";
15
16
 
@@ -24,6 +25,22 @@ function isExternalLinkAction(
24
25
  return press.action === "open_url";
25
26
  }
26
27
 
28
+ function getActionPendingPath(on: Record<string, unknown> | undefined) {
29
+ const press = on?.press as
30
+ | { action?: unknown; params?: Record<string, unknown> }
31
+ | undefined;
32
+ if (!press?.action) return "/__snap/action/pending";
33
+
34
+ return (
35
+ buildActionActivityStateChanges({
36
+ actionName: press.action,
37
+ params: press.params ?? {},
38
+ pending: true,
39
+ }).find((change) => change.path.endsWith("/pending"))?.path ??
40
+ "/__snap/action/pending"
41
+ );
42
+ }
43
+
27
44
  export function SnapActionButton({
28
45
  element,
29
46
  emit,
@@ -38,18 +55,25 @@ export function SnapActionButton({
38
55
  const label = String(props.label ?? "Action");
39
56
  const variant = String(props.variant ?? "secondary");
40
57
  const isPrimary = variant === "primary";
58
+ const disabled = props.disabled === true;
41
59
  const iconName = props.icon ? String(props.icon) : undefined;
42
60
  const colors = useSnapColors();
43
61
  const [hovered, setHovered] = useState(false);
44
62
  const stateStore = useStateStore();
45
63
  const paginatorAction = getPaginatorAction(element.on);
64
+ const actionPendingPath = useMemo(
65
+ () => getActionPendingPath(element.on),
66
+ [element.on],
67
+ );
68
+ const actionPending = useStateValue(actionPendingPath) === true;
46
69
 
47
70
  const Icon = iconName ? ICON_MAP[iconName] : undefined;
48
71
  const showExternalIcon = isExternalLinkAction(element.on);
49
72
  const inHorizontalStack = useSnapStackDirection() === "horizontal";
50
73
 
51
74
  const style = {
52
- cursor: "pointer" as const,
75
+ cursor: disabled ? ("not-allowed" as const) : ("pointer" as const),
76
+ opacity: disabled ? 0.62 : 1,
53
77
  ...(isPrimary
54
78
  ? {
55
79
  backgroundColor: hovered ? colors.accentHover : colors.accent,
@@ -83,8 +107,10 @@ export function SnapActionButton({
83
107
  type="button"
84
108
  variant={isPrimary ? "default" : "secondary"}
85
109
  className={cn("h-8 w-full gap-2 px-3 text-sm")}
110
+ disabled={disabled}
86
111
  style={style}
87
112
  onClick={() => {
113
+ if (disabled) return;
88
114
  if (!runPaginatorAction(stateStore, paginatorAction)) {
89
115
  emit("press");
90
116
  }
@@ -94,6 +120,9 @@ export function SnapActionButton({
94
120
  >
95
121
  {Icon && <Icon size={16} />}
96
122
  {label}
123
+ {actionPending && (
124
+ <span data-snap-action-pending-active="true" hidden />
125
+ )}
97
126
  {showExternalIcon && (
98
127
  <ExternalLink size={14} style={{ opacity: 0.6 }} />
99
128
  )}
@@ -5,6 +5,7 @@ import type { ReactNode } from "react";
5
5
  import type { ValidationResult } from "../validator.js";
6
6
  import { SPEC_VERSION_2 } from "../constants";
7
7
  import type { SnapRenderState } from "../render-state";
8
+ import type { SnapTransactionResult } from "../schemas";
8
9
  import { SnapCardV1 } from "./v1/snap-view";
9
10
  import { SnapCardV2 } from "./v2/snap-view";
10
11
 
@@ -36,18 +37,6 @@ export type SnapSendTransactionParams = {
36
37
  maxPriorityFeePerGas?: string;
37
38
  };
38
39
 
39
- export type SnapSendCallsParams = {
40
- version?: "1.0";
41
- chainId: string;
42
- atomicRequired?: boolean;
43
- id?: string;
44
- calls: Array<{
45
- to?: string;
46
- data?: string;
47
- value?: string;
48
- }>;
49
- };
50
-
51
40
  export type SnapActionHandlers = {
52
41
  submit: (target: string, inputs: Record<string, JsonValue>) => void;
53
42
  open_url: (target: string) => void;
@@ -69,8 +58,9 @@ export type SnapActionHandlers = {
69
58
  recipientAddress?: string;
70
59
  }) => void;
71
60
  swap_token: (params: { sellToken?: string; buyToken?: string }) => void;
72
- send_transaction?: (params: SnapSendTransactionParams) => void;
73
- send_calls?: (params: SnapSendCallsParams) => void;
61
+ send_transaction?: (
62
+ params: SnapSendTransactionParams,
63
+ ) => void | Promise<void | SnapTransactionResult>;
74
64
  };
75
65
 
76
66
  export type { SnapRenderState };