@farcaster/snap 1.17.1 → 1.18.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/dist/schemas.d.ts CHANGED
@@ -68,17 +68,12 @@ export type SnapHandlerResult = {
68
68
  effects?: z.input<typeof snapResponseSchema>["effects"];
69
69
  ui: SnapSpecInput;
70
70
  };
71
- /**
72
- * @deprecated `nonce` and `audience` are currently optional for backward
73
- * compatibility but will become **required** in a future major version.
74
- * Clients should always include both fields.
75
- */
76
71
  export declare const payloadSchema: z.ZodObject<{
77
72
  fid: z.ZodNumber;
78
73
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
79
74
  timestamp: z.ZodNumber;
80
- nonce: z.ZodOptional<z.ZodString>;
81
- audience: z.ZodOptional<z.ZodString>;
75
+ nonce: z.ZodString;
76
+ audience: z.ZodString;
82
77
  }, z.core.$strip>;
83
78
  export type SnapPayload = z.infer<typeof payloadSchema>;
84
79
  export declare const ACTION_TYPE_GET: "get";
@@ -91,8 +86,8 @@ declare const snapPostActionSchema: z.ZodObject<{
91
86
  fid: z.ZodNumber;
92
87
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
93
88
  timestamp: z.ZodNumber;
94
- nonce: z.ZodOptional<z.ZodString>;
95
- audience: z.ZodOptional<z.ZodString>;
89
+ nonce: z.ZodString;
90
+ audience: z.ZodString;
96
91
  type: z.ZodLiteral<"post">;
97
92
  }, z.core.$strip>;
98
93
  export type SnapPostAction = z.infer<typeof snapPostActionSchema>;
@@ -102,8 +97,8 @@ export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
102
97
  fid: z.ZodNumber;
103
98
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
104
99
  timestamp: z.ZodNumber;
105
- nonce: z.ZodOptional<z.ZodString>;
106
- audience: z.ZodOptional<z.ZodString>;
100
+ nonce: z.ZodString;
101
+ audience: z.ZodString;
107
102
  type: z.ZodLiteral<"post">;
108
103
  }, z.core.$strip>], "type">;
109
104
  export type SnapAction = z.infer<typeof snapActionSchema>;
package/dist/schemas.js CHANGED
@@ -31,20 +31,13 @@ const postInputValueSchema = z.union([
31
31
  z.boolean(),
32
32
  z.array(z.string()),
33
33
  ]);
34
- /**
35
- * @deprecated `nonce` and `audience` are currently optional for backward
36
- * compatibility but will become **required** in a future major version.
37
- * Clients should always include both fields.
38
- */
39
34
  export const payloadSchema = z
40
35
  .object({
41
36
  fid: z.number().int().nonnegative(),
42
37
  inputs: z.record(z.string(), postInputValueSchema).default({}),
43
38
  timestamp: z.number().int(),
44
- /** @deprecated Will become required. Clients should always send a unique nonce. */
45
- nonce: z.string().optional(),
46
- /** @deprecated Will become required. Clients should always send the target server origin. */
47
- audience: z.string().optional(),
39
+ nonce: z.string(),
40
+ audience: z.string(),
48
41
  })
49
42
  .strip();
50
43
  export const ACTION_TYPE_GET = "get";
@@ -77,36 +77,29 @@ export async function parseRequest(request, options = {}) {
77
77
  },
78
78
  };
79
79
  }
80
- // Deprecation: nonce and audience will become required in a future major version.
81
- if (body.nonce === undefined || body.audience === undefined) {
82
- console.warn("[snap] POST payload is missing nonce and/or audience. " +
83
- "These fields will be required in a future major version. " +
84
- "Please update your client to include both fields.");
85
- }
86
- if (body.audience !== undefined) {
87
- let expectedOrigin = options.requestOrigin;
88
- if (expectedOrigin === undefined) {
89
- try {
90
- const url = new URL(request.url);
91
- const proto = request.headers.get("x-forwarded-proto") ??
92
- url.protocol.replace(":", "");
93
- const host = request.headers.get("x-forwarded-host") ?? url.host;
94
- expectedOrigin = `${proto}://${host}`;
95
- }
96
- catch {
97
- // do nothing
98
- }
80
+ // Audience validation: ensure the payload audience matches the server origin.
81
+ let expectedOrigin = options.requestOrigin;
82
+ if (expectedOrigin === undefined) {
83
+ try {
84
+ const url = new URL(request.url);
85
+ const proto = request.headers.get("x-forwarded-proto") ??
86
+ url.protocol.replace(":", "");
87
+ const host = request.headers.get("x-forwarded-host") ?? url.host;
88
+ expectedOrigin = `${proto}://${host}`;
99
89
  }
100
- if (expectedOrigin !== undefined && body.audience !== expectedOrigin) {
101
- return {
102
- success: false,
103
- error: {
104
- type: "origin_mismatch",
105
- message: `payload audience "${body.audience}" does not match expected origin "${expectedOrigin}"`,
106
- },
107
- };
90
+ catch {
91
+ // do nothing
108
92
  }
109
93
  }
94
+ if (expectedOrigin !== undefined && body.audience !== expectedOrigin) {
95
+ return {
96
+ success: false,
97
+ error: {
98
+ type: "origin_mismatch",
99
+ message: `payload audience "${body.audience}" does not match expected origin "${expectedOrigin}"`,
100
+ },
101
+ };
102
+ }
110
103
  return {
111
104
  success: true,
112
105
  action: {
@@ -10,7 +10,11 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
10
10
  root: import("@json-render/core").SchemaType<"string", unknown>;
11
11
  elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
12
12
  type: import("@json-render/core").SchemaType<"ref", string>;
13
- props: import("@json-render/core").SchemaType<"propsOf", string>;
13
+ props: {
14
+ optional: true;
15
+ kind: "propsOf";
16
+ inner?: string;
17
+ };
14
18
  children: {
15
19
  optional: true;
16
20
  kind: "array";
@@ -7,7 +7,11 @@ export declare const snapJsonRenderSchema: import("@json-render/core").Schema<{
7
7
  root: import("@json-render/core").SchemaType<"string", unknown>;
8
8
  elements: import("@json-render/core").SchemaType<"record", import("@json-render/core").SchemaType<"object", {
9
9
  type: import("@json-render/core").SchemaType<"ref", string>;
10
- props: import("@json-render/core").SchemaType<"propsOf", string>;
10
+ props: {
11
+ optional: true;
12
+ kind: "propsOf";
13
+ inner?: string;
14
+ };
11
15
  children: {
12
16
  optional: true;
13
17
  kind: "array";
package/dist/ui/schema.js CHANGED
@@ -8,7 +8,7 @@ export const snapJsonRenderSchema = defineSchema((s) => ({
8
8
  root: s.string(),
9
9
  elements: s.record(s.object({
10
10
  type: s.ref("catalog.components"),
11
- props: s.propsOf("catalog.components"),
11
+ props: { ...s.propsOf("catalog.components"), optional: true },
12
12
  children: { ...s.array(s.string()), optional: true },
13
13
  })),
14
14
  }),
package/dist/validator.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { snapResponseSchema } from "./schemas.js";
2
2
  import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1 } from "./constants.js";
3
+ import { snapJsonRenderCatalog } from "./ui/catalog.js";
3
4
  // ─── Helpers ──────────────────────────────────────────
4
5
  /** Actions whose `params.target` must be a valid URL. */
5
6
  const URL_TARGET_ACTIONS = new Set(["submit", "open_url", "open_mini_app"]);
@@ -202,6 +203,10 @@ export function validateSnapResponse(json) {
202
203
  if (urlIssues.length > 0) {
203
204
  return { valid: false, issues: urlIssues };
204
205
  }
206
+ const catalogResult = snapJsonRenderCatalog.validate(ui);
207
+ if (!catalogResult.success) {
208
+ return { valid: false, issues: catalogResult.error?.issues ?? [] };
209
+ }
205
210
  }
206
211
  return { valid: true, issues: [] };
207
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
package/src/schemas.ts CHANGED
@@ -81,20 +81,13 @@ const postInputValueSchema = z.union([
81
81
  z.array(z.string()),
82
82
  ]);
83
83
 
84
- /**
85
- * @deprecated `nonce` and `audience` are currently optional for backward
86
- * compatibility but will become **required** in a future major version.
87
- * Clients should always include both fields.
88
- */
89
84
  export const payloadSchema = z
90
85
  .object({
91
86
  fid: z.number().int().nonnegative(),
92
87
  inputs: z.record(z.string(), postInputValueSchema).default({}),
93
88
  timestamp: z.number().int(),
94
- /** @deprecated Will become required. Clients should always send a unique nonce. */
95
- nonce: z.string().optional(),
96
- /** @deprecated Will become required. Clients should always send the target server origin. */
97
- audience: z.string().optional(),
89
+ nonce: z.string(),
90
+ audience: z.string(),
98
91
  })
99
92
  .strip();
100
93
 
@@ -148,39 +148,29 @@ export async function parseRequest(
148
148
  };
149
149
  }
150
150
 
151
- // Deprecation: nonce and audience will become required in a future major version.
152
- if (body.nonce === undefined || body.audience === undefined) {
153
- console.warn(
154
- "[snap] POST payload is missing nonce and/or audience. " +
155
- "These fields will be required in a future major version. " +
156
- "Please update your client to include both fields.",
157
- );
158
- }
159
-
160
- if (body.audience !== undefined) {
161
- let expectedOrigin = options.requestOrigin;
162
- if (expectedOrigin === undefined) {
163
- try {
164
- const url = new URL(request.url);
165
- const proto =
166
- request.headers.get("x-forwarded-proto") ??
167
- url.protocol.replace(":", "");
168
- const host = request.headers.get("x-forwarded-host") ?? url.host;
169
- expectedOrigin = `${proto}://${host}`;
170
- } catch {
171
- // do nothing
172
- }
151
+ // Audience validation: ensure the payload audience matches the server origin.
152
+ let expectedOrigin = options.requestOrigin;
153
+ if (expectedOrigin === undefined) {
154
+ try {
155
+ const url = new URL(request.url);
156
+ const proto =
157
+ request.headers.get("x-forwarded-proto") ??
158
+ url.protocol.replace(":", "");
159
+ const host = request.headers.get("x-forwarded-host") ?? url.host;
160
+ expectedOrigin = `${proto}://${host}`;
161
+ } catch {
162
+ // do nothing
173
163
  }
164
+ }
174
165
 
175
- if (expectedOrigin !== undefined && body.audience !== expectedOrigin) {
176
- return {
177
- success: false,
178
- error: {
179
- type: "origin_mismatch",
180
- message: `payload audience "${body.audience}" does not match expected origin "${expectedOrigin}"`,
181
- },
182
- };
183
- }
166
+ if (expectedOrigin !== undefined && body.audience !== expectedOrigin) {
167
+ return {
168
+ success: false,
169
+ error: {
170
+ type: "origin_mismatch",
171
+ message: `payload audience "${body.audience}" does not match expected origin "${expectedOrigin}"`,
172
+ },
173
+ };
184
174
  }
185
175
 
186
176
  return {
package/src/ui/schema.ts CHANGED
@@ -11,7 +11,7 @@ export const snapJsonRenderSchema = defineSchema(
11
11
  elements: s.record(
12
12
  s.object({
13
13
  type: s.ref("catalog.components"),
14
- props: s.propsOf("catalog.components"),
14
+ props: { ...s.propsOf("catalog.components"), optional: true },
15
15
  children: { ...s.array(s.string()), optional: true },
16
16
  }),
17
17
  ),
package/src/validator.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { snapResponseSchema } from "./schemas";
3
3
  import { MAX_CHILDREN, MAX_DEPTH, MAX_ELEMENTS, MAX_ROOT_CHILDREN, SPEC_VERSION_1 } from "./constants";
4
+ import { snapJsonRenderCatalog } from "./ui/catalog.js";
4
5
 
5
6
  export type ValidationResult = {
6
7
  valid: boolean;
@@ -255,6 +256,11 @@ export function validateSnapResponse(json: unknown): ValidationResult {
255
256
  if (urlIssues.length > 0) {
256
257
  return { valid: false, issues: urlIssues };
257
258
  }
259
+
260
+ const catalogResult = snapJsonRenderCatalog.validate(ui);
261
+ if (!catalogResult.success) {
262
+ return { valid: false, issues: catalogResult.error?.issues ?? [] };
263
+ }
258
264
  }
259
265
 
260
266
  return { valid: true, issues: [] };