@farcaster/snap 2.1.1 → 2.2.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 (68) hide show
  1. package/dist/colors.d.ts +10 -0
  2. package/dist/colors.js +22 -0
  3. package/dist/constants.d.ts +3 -1
  4. package/dist/constants.js +7 -2
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.js +3 -3
  7. package/dist/react/components/cell-grid.js +13 -14
  8. package/dist/react/components/image.js +5 -1
  9. package/dist/react/components/stack.js +53 -3
  10. package/dist/react/components/text.js +7 -1
  11. package/dist/react/hooks/use-snap-colors.js +2 -9
  12. package/dist/react/stack-direction-context.d.ts +7 -0
  13. package/dist/react/stack-direction-context.js +10 -0
  14. package/dist/react-native/components/snap-cell-grid.js +5 -7
  15. package/dist/react-native/components/snap-image.js +15 -2
  16. package/dist/react-native/components/snap-item.js +12 -2
  17. package/dist/react-native/components/snap-progress.js +8 -2
  18. package/dist/react-native/components/snap-stack.d.ts +1 -1
  19. package/dist/react-native/components/snap-stack.js +85 -10
  20. package/dist/react-native/components/snap-text.js +7 -2
  21. package/dist/react-native/stack-direction-context.d.ts +7 -0
  22. package/dist/react-native/stack-direction-context.js +9 -0
  23. package/dist/react-native/use-snap-palette.js +2 -2
  24. package/dist/schemas.d.ts +52 -0
  25. package/dist/schemas.js +10 -4
  26. package/dist/server/index.d.ts +2 -1
  27. package/dist/server/index.js +2 -1
  28. package/dist/server/parseRequest.d.ts +4 -3
  29. package/dist/server/parseRequest.js +91 -67
  30. package/dist/server/verify.d.ts +12 -5
  31. package/dist/server/verify.js +67 -19
  32. package/dist/stack-horizontal-utils.d.ts +4 -0
  33. package/dist/stack-horizontal-utils.js +29 -0
  34. package/dist/ui/catalog.d.ts +3 -2
  35. package/dist/ui/catalog.js +2 -2
  36. package/dist/ui/cell-grid.d.ts +2 -2
  37. package/dist/ui/cell-grid.js +11 -2
  38. package/dist/ui/stack.d.ts +1 -0
  39. package/dist/ui/stack.js +8 -0
  40. package/dist/verify.test.js +3 -3
  41. package/llms.txt +13 -2
  42. package/package.json +1 -1
  43. package/src/colors.ts +27 -0
  44. package/src/constants.ts +8 -2
  45. package/src/index.ts +6 -0
  46. package/src/react/components/cell-grid.tsx +17 -24
  47. package/src/react/components/image.tsx +8 -1
  48. package/src/react/components/stack.tsx +84 -11
  49. package/src/react/components/text.tsx +8 -1
  50. package/src/react/hooks/use-snap-colors.ts +3 -8
  51. package/src/react/stack-direction-context.tsx +27 -0
  52. package/src/react-native/components/snap-cell-grid.tsx +5 -11
  53. package/src/react-native/components/snap-image.tsx +17 -2
  54. package/src/react-native/components/snap-item.tsx +14 -2
  55. package/src/react-native/components/snap-progress.tsx +8 -2
  56. package/src/react-native/components/snap-stack.tsx +116 -14
  57. package/src/react-native/components/snap-text.tsx +7 -2
  58. package/src/react-native/stack-direction-context.tsx +25 -0
  59. package/src/react-native/use-snap-palette.ts +2 -1
  60. package/src/schemas.ts +14 -4
  61. package/src/server/index.ts +7 -1
  62. package/src/server/parseRequest.ts +117 -71
  63. package/src/server/verify.ts +99 -26
  64. package/src/stack-horizontal-utils.ts +27 -0
  65. package/src/ui/catalog.ts +2 -2
  66. package/src/ui/cell-grid.ts +15 -2
  67. package/src/ui/stack.ts +8 -0
  68. package/src/verify.test.ts +3 -3
@@ -1,6 +1,14 @@
1
1
  import type { ComponentRenderProps } from "@json-render/react-native";
2
- import type { ReactNode } from "react";
2
+ import { Children, type ReactNode } from "react";
3
3
  import { StyleSheet, View } from "react-native";
4
+ import {
5
+ countRenderableChildren,
6
+ horizontalChildrenAreAllButtons,
7
+ } from "../../stack-horizontal-utils.js";
8
+ import {
9
+ SnapStackDirectionProvider,
10
+ useSnapStackDirection,
11
+ } from "../stack-direction-context";
4
12
 
5
13
  const VGAP: Record<string, number> = {
6
14
  none: 0,
@@ -24,10 +32,23 @@ const JUSTIFY: Record<string, "flex-start" | "center" | "flex-end" | "space-betw
24
32
  around: "space-around",
25
33
  };
26
34
 
35
+ /** Equal-width cells for explicit `columns` and all-button horizontal rows. */
36
+ function wrapEqualColumnCells(children: ReactNode): ReactNode {
37
+ const cells = Children.toArray(children).filter(
38
+ (c) => c != null && c !== false,
39
+ );
40
+ return cells.map((child, i) => (
41
+ <View key={i} style={styles.equalColumnCell}>
42
+ {child}
43
+ </View>
44
+ ));
45
+ }
46
+
27
47
  export function SnapStack({
28
48
  element: { props },
29
49
  children,
30
50
  }: ComponentRenderProps<Record<string, unknown>> & { children?: ReactNode }) {
51
+ const parentDirection = useSnapStackDirection();
31
52
  const direction = String(props.direction ?? "vertical");
32
53
  const rawGap = props.gap;
33
54
  const isHorizontal = direction === "horizontal";
@@ -38,29 +59,110 @@ export function SnapStack({
38
59
  : typeof rawGap === "string" && rawGap in gapMap
39
60
  ? gapMap[rawGap]!
40
61
  : isHorizontal ? HGAP.md! : VGAP.md!;
41
- const justify = props.justify ? JUSTIFY[String(props.justify)] : undefined;
62
+ const buttonRowGrid =
63
+ isHorizontal && horizontalChildrenAreAllButtons(children);
64
+ const buttonRowCount = buttonRowGrid
65
+ ? countRenderableChildren(children)
66
+ : 0;
67
+
68
+ const columnsRaw = props.columns;
69
+ const columns =
70
+ typeof columnsRaw === "number" &&
71
+ columnsRaw >= 2 &&
72
+ columnsRaw <= 6 &&
73
+ Number.isInteger(columnsRaw)
74
+ ? columnsRaw
75
+ : undefined;
76
+ const explicitColumnGrid =
77
+ isHorizontal && columns !== undefined && !buttonRowGrid;
78
+
79
+ const justify =
80
+ props.justify &&
81
+ (!isHorizontal || (!buttonRowGrid && !explicitColumnGrid))
82
+ ? JUSTIFY[String(props.justify)]
83
+ : undefined;
84
+
85
+ const isRowChild = parentDirection === "horizontal";
86
+
87
+ const packedHorizontal =
88
+ isHorizontal &&
89
+ ((buttonRowGrid &&
90
+ buttonRowCount >= 1 &&
91
+ buttonRowCount <= 6) ||
92
+ explicitColumnGrid);
93
+
94
+ let horizontalBody: ReactNode = children;
95
+ if (
96
+ isHorizontal &&
97
+ buttonRowGrid &&
98
+ buttonRowCount >= 1 &&
99
+ buttonRowCount <= 6
100
+ ) {
101
+ horizontalBody = wrapEqualColumnCells(children);
102
+ } else if (isHorizontal && explicitColumnGrid && columns !== undefined) {
103
+ horizontalBody = wrapEqualColumnCells(children);
104
+ }
42
105
 
43
106
  return (
44
- <View
45
- style={[
46
- styles.stack,
47
- isHorizontal ? styles.horizontal : undefined,
48
- { gap },
49
- justify ? { justifyContent: justify } : undefined,
50
- ]}
107
+ <SnapStackDirectionProvider
108
+ direction={isHorizontal ? "horizontal" : "vertical"}
51
109
  >
52
- {children}
53
- </View>
110
+ <View
111
+ style={[
112
+ isRowChild ? styles.stackRowChild : styles.stack,
113
+ isHorizontal
114
+ ? packedHorizontal
115
+ ? styles.horizontalPacked
116
+ : styles.horizontalDefault
117
+ : styles.verticalStack,
118
+ { gap },
119
+ justify ? { justifyContent: justify } : undefined,
120
+ ]}
121
+ >
122
+ {horizontalBody}
123
+ </View>
124
+ </SnapStackDirectionProvider>
54
125
  );
55
126
  }
56
127
 
57
128
  const styles = StyleSheet.create({
58
129
  stack: {
59
130
  width: "100%",
131
+ minWidth: 0,
132
+ },
133
+ verticalStack: {
134
+ width: "100%",
135
+ minWidth: 0,
60
136
  },
61
- horizontal: {
137
+ /** Nested stack inside a horizontal row — share width with siblings (matches web flex peers). */
138
+ stackRowChild: {
139
+ flexGrow: 1,
140
+ flexShrink: 1,
141
+ flexBasis: 0,
142
+ minWidth: 0,
143
+ maxWidth: "100%",
144
+ alignSelf: "stretch",
145
+ },
146
+ /** Default horizontal row: single line, equal-height peers. */
147
+ horizontalDefault: {
62
148
  flexDirection: "row",
63
- alignItems: "center",
64
- flexWrap: "wrap",
149
+ alignItems: "stretch",
150
+ flexWrap: "nowrap",
151
+ width: "100%",
152
+ minWidth: 0,
153
+ },
154
+ /** Single row for packed equal-width cells (button grids & explicit columns). */
155
+ horizontalPacked: {
156
+ flexDirection: "row",
157
+ flexWrap: "nowrap",
158
+ alignItems: "stretch",
159
+ width: "100%",
160
+ minWidth: 0,
161
+ },
162
+ equalColumnCell: {
163
+ flexGrow: 1,
164
+ flexShrink: 1,
165
+ flexBasis: 0,
166
+ minWidth: 0,
65
167
  },
66
168
  });
@@ -1,5 +1,6 @@
1
1
  import type { ComponentRenderProps } from "@json-render/react-native";
2
2
  import { StyleSheet, Text, View } from "react-native";
3
+ import { useSnapStackDirection } from "../stack-direction-context";
3
4
  import { useSnapTheme } from "../theme";
4
5
 
5
6
  const SIZE_STYLES: Record<string, { fontSize: number; lineHeight?: number; fontWeight?: "400" | "500" | "600" | "700" }> = {
@@ -24,9 +25,10 @@ export function SnapText({
24
25
  const sizeStyle = SIZE_STYLES[size] ?? SIZE_STYLES.md;
25
26
  const resolvedWeight = weight ? WEIGHT_MAP[weight] : sizeStyle?.fontWeight;
26
27
  const textAlign = align === "center" ? "center" : align === "right" ? "right" : "left";
28
+ const inHorizontalStack = useSnapStackDirection() === "horizontal";
27
29
 
28
30
  return (
29
- <View style={styles.wrap}>
31
+ <View style={inHorizontalStack ? styles.wrapRow : styles.wrapCol}>
30
32
  <Text
31
33
  style={[
32
34
  styles.base,
@@ -46,6 +48,9 @@ export function SnapText({
46
48
  }
47
49
 
48
50
  const styles = StyleSheet.create({
49
- wrap: { width: "100%" },
51
+ /** Full width for vertical stacks (alignment / wrapping). */
52
+ wrapCol: { width: "100%" },
53
+ /** Row peers: hug content; avoid width 100% fighting nowrap horizontal rows. */
54
+ wrapRow: { flexShrink: 1, minWidth: 0 },
50
55
  base: {},
51
56
  });
@@ -0,0 +1,25 @@
1
+ import { createContext, useContext, type ReactNode } from "react";
2
+
3
+ export type SnapStackDirection = "vertical" | "horizontal";
4
+
5
+ const SnapStackDirectionContext = createContext<SnapStackDirection | undefined>(
6
+ undefined,
7
+ );
8
+
9
+ export function SnapStackDirectionProvider({
10
+ direction,
11
+ children,
12
+ }: {
13
+ direction: SnapStackDirection;
14
+ children: ReactNode;
15
+ }) {
16
+ return (
17
+ <SnapStackDirectionContext.Provider value={direction}>
18
+ {children}
19
+ </SnapStackDirectionContext.Provider>
20
+ );
21
+ }
22
+
23
+ export function useSnapStackDirection(): SnapStackDirection | undefined {
24
+ return useContext(SnapStackDirectionContext);
25
+ }
@@ -3,6 +3,7 @@ import {
3
3
  PALETTE_COLOR_VALUES,
4
4
  PALETTE_LIGHT_HEX,
5
5
  PALETTE_DARK_HEX,
6
+ resolveSnapColorHex,
6
7
  type PaletteColor,
7
8
  } from "@farcaster/snap";
8
9
  import { useStateStore } from "@json-render/react-native";
@@ -35,7 +36,7 @@ export function useSnapPalette() {
35
36
  const accentHex = resolveHex(accentName, mode);
36
37
 
37
38
  const hex = (semantic: string) =>
38
- semantic === "accent" ? accentHex : resolveHex(semantic, mode);
39
+ resolveSnapColorHex(semantic, { accentHex, appearance: mode });
39
40
 
40
41
  return { appearance: mode, accentName, accentHex, hex };
41
42
  }
package/src/schemas.ts CHANGED
@@ -104,26 +104,36 @@ const surfaceSchema = z.discriminatedUnion("type", [
104
104
  standaloneSurfaceSchema,
105
105
  ]);
106
106
 
107
+ const fidSchema = z.number().int().nonnegative();
108
+ const userSchema = z.object({ fid: fidSchema });
109
+
107
110
  export const payloadSchema = z
108
111
  .object({
109
- fid: z.number().int().nonnegative().optional(), // deprecated in favor of user.fid
112
+ fid: fidSchema.optional(), // deprecated in favor of user.fid
110
113
  inputs: z.record(z.string(), postInputValueSchema).default({}),
111
114
  timestamp: z.number().int(),
112
115
  audience: z.string(),
113
- user: z.object({
114
- fid: z.number().int().nonnegative(),
115
- }),
116
+ user: userSchema,
116
117
  surface: surfaceSchema,
117
118
  })
118
119
  .strip();
119
120
 
120
121
  export type SnapPayload = z.infer<typeof payloadSchema>;
121
122
 
123
+ /** JFS payload shape for POST minus deprecated `fid`; used for GET auth via payload header. */
124
+ export const getPayloadSchema = payloadSchema.omit({ inputs: true, fid: true });
125
+
126
+ export type SnapGetPayload = z.infer<typeof getPayloadSchema>;
127
+
122
128
  export const ACTION_TYPE_GET = "get" as const;
123
129
  export const ACTION_TYPE_POST = "post" as const;
124
130
 
125
131
  const snapGetActionSchema = z.object({
126
132
  type: z.literal(ACTION_TYPE_GET),
133
+ user: userSchema.optional(),
134
+ timestamp: z.number().int().optional(),
135
+ audience: z.string().optional(),
136
+ surface: surfaceSchema.optional(),
127
137
  });
128
138
 
129
139
  export type SnapGetAction = z.infer<typeof snapGetActionSchema>;
@@ -1,4 +1,10 @@
1
- export { verifyJFSRequestBody, decodePayload, encodePayload } from "./verify";
1
+ export {
2
+ verifyJFS as verifyJFSRequestBody, // deprecated alias. drop in v3
3
+ parseJfs,
4
+ verifyJFS,
5
+ decodePayload,
6
+ encodePayload,
7
+ } from "./verify";
2
8
  export {
3
9
  DEFAULT_SNAP_HUB_HTTP_BASE_URL,
4
10
  getActiveEd25519SignerKeysFromHubHttp,
@@ -1,11 +1,15 @@
1
+ import { z } from "zod";
1
2
  import {
2
3
  ACTION_TYPE_GET,
3
4
  ACTION_TYPE_POST,
5
+ getPayloadSchema,
4
6
  payloadSchema,
5
7
  type SnapAction,
8
+ type SnapPayload,
9
+ type SnapGetPayload,
6
10
  } from "../schemas";
7
- import { decodePayload, verifyJFSRequestBody } from "./verify";
8
- import { z } from "zod";
11
+ import { decodePayload, parseJfs, verifyJFS } from "./verify";
12
+ import { SNAP_PAYLOAD_HEADER } from "../constants";
9
13
 
10
14
  const DEFAULT_SNAP_POST_MAX_SKEW_SECONDS = 300 as const;
11
15
 
@@ -62,98 +66,156 @@ export type ParseRequestResult =
62
66
  | { success: true; action: SnapAction }
63
67
  | { success: false; error: ParseRequestError };
64
68
 
65
- const requestBodySchema = z.object({
66
- header: z.string(),
67
- payload: z.string(),
68
- signature: z.string(),
69
- });
70
-
71
69
  /**
72
70
  * Parse and validate Farcaster snap requests:
73
- * - `GET` is allowed for first-page loads and returns `{ type: "get" }`.
74
- * - `POST`: the body must be JSON in JFS form (`header` / `payload` / `signature`) even if JFS verification is skipped.
71
+ * - `GET`: returns `{ type: "get" }`, or optional viewer fields when `X-Snap-Payload`
72
+ * carries a JFS compact string whose decoded payload validates against {@link getPayloadSchema}.
73
+ * - `POST`: the body must be a JFS envelope — either JSON `{ header, payload, signature }` or the same **compact** string form as GET (`BASE64URL(header).BASE64URL(payload).BASE64URL(signature)`), even if JFS verification is skipped.
75
74
  */
76
75
  export async function parseRequest(
77
76
  request: Request,
78
77
  options: ParseRequestOptions = {},
79
78
  ): Promise<ParseRequestResult> {
80
- if (!["GET", "POST"].includes(request.method)) {
81
- return {
82
- success: false,
83
- error: {
84
- type: "method_not_allowed",
85
- message: `expected POST, received ${request.method}`,
86
- },
87
- };
79
+ if (request.method === "GET") {
80
+ return await parseGetRequest(request, options);
81
+ }
82
+ if (request.method === "POST") {
83
+ return await parsePostRequest(request, options);
88
84
  }
85
+ return {
86
+ success: false,
87
+ error: {
88
+ type: "method_not_allowed",
89
+ message: `expected GET or POST, received ${request.method}`,
90
+ },
91
+ };
92
+ }
89
93
 
90
- if (request.method === "GET") {
91
- return {
92
- success: true,
93
- action: { type: ACTION_TYPE_GET },
94
- };
94
+ async function parseGetRequest(
95
+ request: Request,
96
+ options: ParseRequestOptions,
97
+ ): Promise<ParseRequestResult> {
98
+ const compactHeader = request.headers.get(SNAP_PAYLOAD_HEADER)?.trim();
99
+ if (!compactHeader) {
100
+ return { success: true, action: { type: ACTION_TYPE_GET } };
95
101
  }
96
102
 
97
- const maxSkew = options.maxSkewSeconds ?? DEFAULT_SNAP_POST_MAX_SKEW_SECONDS;
98
- const nowSec = Math.floor(Date.now() / 1000);
103
+ const result = await validateJfsPayload({
104
+ jfsText: compactHeader,
105
+ schema: getPayloadSchema,
106
+ request,
107
+ options,
108
+ invalidJsonMessage: `${SNAP_PAYLOAD_HEADER} must be a valid JFS compact string`,
109
+ });
110
+ if (!result.ok) {
111
+ return { success: false, error: result.error };
112
+ }
99
113
 
100
- const text = await request.text();
114
+ return {
115
+ success: true,
116
+ action: { type: ACTION_TYPE_GET, ...result.payload },
117
+ };
118
+ }
101
119
 
102
- let jsonBody: unknown;
103
- try {
104
- jsonBody = JSON.parse(text);
105
- } catch {
120
+ async function parsePostRequest(
121
+ request: Request,
122
+ options: ParseRequestOptions,
123
+ ): Promise<ParseRequestResult> {
124
+ const result = await validateJfsPayload({
125
+ jfsText: await request.text(),
126
+ schema: payloadSchema,
127
+ request,
128
+ options,
129
+ });
130
+ if (!result.ok) {
131
+ return { success: false, error: result.error };
132
+ }
133
+
134
+ const payload = result.payload;
135
+ if (payload.fid !== undefined && payload.fid !== payload.user.fid) {
106
136
  return {
107
137
  success: false,
108
138
  error: {
109
- type: "invalid_json",
110
- message: "request body is not valid JSON",
139
+ type: "fid_mismatch",
140
+ message: `fid "${payload.fid}" does not match user.fid "${payload.user.fid}"`,
111
141
  },
112
142
  };
113
143
  }
114
144
 
115
- const parsed = requestBodySchema.safeParse(jsonBody);
116
- if (!parsed.success) {
145
+ return {
146
+ success: true,
147
+ action: { type: ACTION_TYPE_POST, ...payload },
148
+ };
149
+ }
150
+
151
+ /**
152
+ * Shared pipeline for authenticated snap requests: parse the JFS envelope,
153
+ * decode and schema-validate the payload, optionally verify the JFS signature
154
+ * against an active hub signer (matching `user.fid`), then check timestamp
155
+ * skew and that `audience` matches the request origin.
156
+ *
157
+ * Both GET (payload header) and POST (request body) feed into this.
158
+ */
159
+ async function validateJfsPayload<T extends SnapPayload | SnapGetPayload>({
160
+ jfsText,
161
+ schema,
162
+ request,
163
+ options,
164
+ invalidJsonMessage,
165
+ }: {
166
+ jfsText: string;
167
+ schema: z.ZodType<T>;
168
+ request: Request;
169
+ options: ParseRequestOptions;
170
+ invalidJsonMessage?: string;
171
+ }): Promise<
172
+ { ok: true; payload: T } | { ok: false; error: ParseRequestError }
173
+ > {
174
+ const parsed = parseJfs(jfsText);
175
+ if (!parsed.ok) {
117
176
  return {
118
- success: false,
119
- error: { type: "invalid_json", message: parsed.error.message },
177
+ ok: false,
178
+ error: {
179
+ type: "invalid_json",
180
+ message: invalidJsonMessage ?? parsed.error,
181
+ },
120
182
  };
121
183
  }
184
+ const jfs = parsed.jfs;
122
185
 
123
- const payloadParsed = payloadSchema.safeParse(
124
- decodePayload(parsed.data.payload),
125
- );
186
+ const payloadParsed = schema.safeParse(decodePayload(jfs.payload));
126
187
  if (!payloadParsed.success) {
127
188
  return {
128
- success: false,
189
+ ok: false,
129
190
  error: { type: "validation", issues: payloadParsed.error.issues },
130
191
  };
131
192
  }
132
-
133
- const body = payloadParsed.data;
193
+ const payload = payloadParsed.data;
134
194
 
135
195
  if (!options.skipJFSVerification) {
136
- const jfs = await verifyJFSRequestBody(parsed.data);
137
- if (!jfs.valid) {
196
+ const verified = await verifyJFS(jfs);
197
+ if (!verified.valid) {
138
198
  return {
139
- success: false,
140
- error: { type: "signature", message: jfs.error.message },
199
+ ok: false,
200
+ error: { type: "signature", message: verified.error.message },
141
201
  };
142
202
  }
143
- if (jfs.signingUserFid !== body.user.fid) {
203
+ if (verified.signingUserFid !== payload.user.fid) {
144
204
  return {
145
- success: false,
205
+ ok: false,
146
206
  error: {
147
207
  type: "fid_mismatch",
148
- message: `JFS header fid "${jfs.signingUserFid}" does not match user.fid "${body.user.fid}"`,
208
+ message: `JFS header fid "${verified.signingUserFid}" does not match user.fid "${payload.user.fid}"`,
149
209
  },
150
210
  };
151
211
  }
152
212
  }
153
213
 
154
- if (Math.abs(nowSec - body.timestamp) > maxSkew) {
214
+ const maxSkew = options.maxSkewSeconds ?? DEFAULT_SNAP_POST_MAX_SKEW_SECONDS;
215
+ const nowSec = Math.floor(Date.now() / 1000);
216
+ if (Math.abs(nowSec - payload.timestamp) > maxSkew) {
155
217
  return {
156
- success: false,
218
+ ok: false,
157
219
  error: {
158
220
  type: "replay",
159
221
  message: `timestamp outside allowed skew of ${maxSkew}s`,
@@ -176,31 +238,15 @@ export async function parseRequest(
176
238
  }
177
239
  }
178
240
 
179
- if (expectedOrigin !== undefined && body.audience !== expectedOrigin) {
241
+ if (expectedOrigin !== undefined && payload.audience !== expectedOrigin) {
180
242
  return {
181
- success: false,
243
+ ok: false,
182
244
  error: {
183
245
  type: "origin_mismatch",
184
- message: `payload audience "${body.audience}" does not match expected origin "${expectedOrigin}"`,
246
+ message: `payload audience "${payload.audience}" does not match expected origin "${expectedOrigin}"`,
185
247
  },
186
248
  };
187
249
  }
188
250
 
189
- if (body.fid !== undefined && body.fid !== body.user.fid) {
190
- return {
191
- success: false,
192
- error: {
193
- type: "fid_mismatch",
194
- message: `fid "${body.fid}" does not match user.fid "${body.user.fid}"`,
195
- },
196
- };
197
- }
198
-
199
- return {
200
- success: true,
201
- action: {
202
- type: ACTION_TYPE_POST,
203
- ...body,
204
- },
205
- };
251
+ return { ok: true, payload };
206
252
  }