@farcaster/snap 1.22.1 → 2.0.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.
@@ -39,7 +39,7 @@ export function applyStatePaths(model, changes) {
39
39
  }
40
40
  }
41
41
  const CONFETTI_COLORS = [
42
- "#8B5CF6",
42
+ "#907AA9",
43
43
  "#EC4899",
44
44
  "#3B82F6",
45
45
  "#10B981",
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef } from "react";
3
3
  import { Animated, StyleSheet, View, useWindowDimensions, } from "react-native";
4
4
  const CONFETTI_COLORS = [
5
- "#8B5CF6",
5
+ "#907AA9",
6
6
  "#EC4899",
7
7
  "#3B82F6",
8
8
  "#10B981",
package/dist/schemas.d.ts CHANGED
@@ -69,11 +69,24 @@ export type SnapHandlerResult = {
69
69
  ui: SnapSpecInput;
70
70
  };
71
71
  export declare const payloadSchema: z.ZodObject<{
72
- fid: z.ZodNumber;
72
+ fid: z.ZodOptional<z.ZodNumber>;
73
73
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
74
74
  timestamp: z.ZodNumber;
75
- nonce: z.ZodString;
76
75
  audience: z.ZodString;
76
+ user: z.ZodObject<{
77
+ fid: z.ZodNumber;
78
+ }, z.core.$strip>;
79
+ surface: z.ZodDiscriminatedUnion<[z.ZodObject<{
80
+ type: z.ZodLiteral<"cast">;
81
+ cast: z.ZodObject<{
82
+ hash: z.ZodString;
83
+ author: z.ZodObject<{
84
+ fid: z.ZodNumber;
85
+ }, z.core.$strip>;
86
+ }, z.core.$strip>;
87
+ }, z.core.$strip>, z.ZodObject<{
88
+ type: z.ZodLiteral<"standalone">;
89
+ }, z.core.$strip>], "type">;
77
90
  }, z.core.$strip>;
78
91
  export type SnapPayload = z.infer<typeof payloadSchema>;
79
92
  export declare const ACTION_TYPE_GET: "get";
@@ -83,22 +96,48 @@ declare const snapGetActionSchema: z.ZodObject<{
83
96
  }, z.core.$strip>;
84
97
  export type SnapGetAction = z.infer<typeof snapGetActionSchema>;
85
98
  declare const snapPostActionSchema: z.ZodObject<{
86
- fid: z.ZodNumber;
99
+ fid: z.ZodOptional<z.ZodNumber>;
87
100
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
88
101
  timestamp: z.ZodNumber;
89
- nonce: z.ZodString;
90
102
  audience: z.ZodString;
103
+ user: z.ZodObject<{
104
+ fid: z.ZodNumber;
105
+ }, z.core.$strip>;
106
+ surface: z.ZodDiscriminatedUnion<[z.ZodObject<{
107
+ type: z.ZodLiteral<"cast">;
108
+ cast: z.ZodObject<{
109
+ hash: z.ZodString;
110
+ author: z.ZodObject<{
111
+ fid: z.ZodNumber;
112
+ }, z.core.$strip>;
113
+ }, z.core.$strip>;
114
+ }, z.core.$strip>, z.ZodObject<{
115
+ type: z.ZodLiteral<"standalone">;
116
+ }, z.core.$strip>], "type">;
91
117
  type: z.ZodLiteral<"post">;
92
118
  }, z.core.$strip>;
93
119
  export type SnapPostAction = z.infer<typeof snapPostActionSchema>;
94
120
  export declare const snapActionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
95
121
  type: z.ZodLiteral<"get">;
96
122
  }, z.core.$strip>, z.ZodObject<{
97
- fid: z.ZodNumber;
123
+ fid: z.ZodOptional<z.ZodNumber>;
98
124
  inputs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodString>]>>>;
99
125
  timestamp: z.ZodNumber;
100
- nonce: z.ZodString;
101
126
  audience: z.ZodString;
127
+ user: z.ZodObject<{
128
+ fid: z.ZodNumber;
129
+ }, z.core.$strip>;
130
+ surface: z.ZodDiscriminatedUnion<[z.ZodObject<{
131
+ type: z.ZodLiteral<"cast">;
132
+ cast: z.ZodObject<{
133
+ hash: z.ZodString;
134
+ author: z.ZodObject<{
135
+ fid: z.ZodNumber;
136
+ }, z.core.$strip>;
137
+ }, z.core.$strip>;
138
+ }, z.core.$strip>, z.ZodObject<{
139
+ type: z.ZodLiteral<"standalone">;
140
+ }, z.core.$strip>], "type">;
102
141
  type: z.ZodLiteral<"post">;
103
142
  }, z.core.$strip>], "type">;
104
143
  export type SnapAction = z.infer<typeof snapActionSchema>;
package/dist/schemas.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { EFFECT_VALUES, SUPPORTED_SPEC_VERSIONS } from "./constants.js";
2
+ import { EFFECT_VALUES, SUPPORTED_SPEC_VERSIONS, } from "./constants.js";
3
3
  import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES } from "./colors.js";
4
4
  // ─── Theme ─────────────────────────────────────────────
5
5
  const themeAccentSchema = z.enum(PALETTE_COLOR_VALUES, {
@@ -31,13 +31,32 @@ const postInputValueSchema = z.union([
31
31
  z.boolean(),
32
32
  z.array(z.string()),
33
33
  ]);
34
+ const standaloneSurfaceSchema = z.object({
35
+ type: z.literal("standalone"),
36
+ });
37
+ const castSurfaceSchema = z.object({
38
+ type: z.literal("cast"),
39
+ cast: z.object({
40
+ hash: z.string(),
41
+ author: z.object({
42
+ fid: z.number().int().nonnegative(),
43
+ }),
44
+ }),
45
+ });
46
+ const surfaceSchema = z.discriminatedUnion("type", [
47
+ castSurfaceSchema,
48
+ standaloneSurfaceSchema,
49
+ ]);
34
50
  export const payloadSchema = z
35
51
  .object({
36
- fid: z.number().int().nonnegative(),
52
+ fid: z.number().int().nonnegative().optional(), // deprecated in favor of user.fid
37
53
  inputs: z.record(z.string(), postInputValueSchema).default({}),
38
54
  timestamp: z.number().int(),
39
- nonce: z.string(),
40
55
  audience: z.string(),
56
+ user: z.object({
57
+ fid: z.number().int().nonnegative(),
58
+ }),
59
+ surface: surfaceSchema,
41
60
  })
42
61
  .strip();
43
62
  export const ACTION_TYPE_GET = "get";
@@ -18,6 +18,9 @@ export type ParseRequestError = {
18
18
  } | {
19
19
  type: "origin_mismatch";
20
20
  message: string;
21
+ } | {
22
+ type: "fid_mismatch";
23
+ message: string;
21
24
  };
22
25
  export type ParseRequestOptions = {
23
26
  /**
@@ -51,6 +51,14 @@ export async function parseRequest(request, options = {}) {
51
51
  error: { type: "invalid_json", message: parsed.error.message },
52
52
  };
53
53
  }
54
+ const payloadParsed = payloadSchema.safeParse(decodePayload(parsed.data.payload));
55
+ if (!payloadParsed.success) {
56
+ return {
57
+ success: false,
58
+ error: { type: "validation", issues: payloadParsed.error.issues },
59
+ };
60
+ }
61
+ const body = payloadParsed.data;
54
62
  if (!options.skipJFSVerification) {
55
63
  const jfs = await verifyJFSRequestBody(parsed.data);
56
64
  if (!jfs.valid) {
@@ -59,15 +67,16 @@ export async function parseRequest(request, options = {}) {
59
67
  error: { type: "signature", message: jfs.error.message },
60
68
  };
61
69
  }
70
+ if (jfs.signingUserFid !== body.user.fid) {
71
+ return {
72
+ success: false,
73
+ error: {
74
+ type: "fid_mismatch",
75
+ message: `JFS header fid "${jfs.signingUserFid}" does not match user.fid "${body.user.fid}"`,
76
+ },
77
+ };
78
+ }
62
79
  }
63
- const payloadParsed = payloadSchema.safeParse(decodePayload(parsed.data.payload));
64
- if (!payloadParsed.success) {
65
- return {
66
- success: false,
67
- error: { type: "validation", issues: payloadParsed.error.issues },
68
- };
69
- }
70
- const body = payloadParsed.data;
71
80
  if (Math.abs(nowSec - body.timestamp) > maxSkew) {
72
81
  return {
73
82
  success: false,
@@ -100,6 +109,15 @@ export async function parseRequest(request, options = {}) {
100
109
  },
101
110
  };
102
111
  }
112
+ if (body.fid !== undefined && body.fid !== body.user.fid) {
113
+ return {
114
+ success: false,
115
+ error: {
116
+ type: "fid_mismatch",
117
+ message: `fid "${body.fid}" does not match user.fid "${body.user.fid}"`,
118
+ },
119
+ };
120
+ }
103
121
  return {
104
122
  success: true,
105
123
  action: {
@@ -9,6 +9,7 @@ export declare function verifyJFSRequestBody<TPayload>(requestBody: {
9
9
  error: Error;
10
10
  } | {
11
11
  valid: true;
12
+ signingUserFid: number;
12
13
  data: TPayload;
13
14
  }>;
14
15
  export declare function decodePayload<TPayload>(payload: string): TPayload;
@@ -69,6 +69,7 @@ export async function verifyJFSRequestBody(requestBody, options = {}) {
69
69
  return {
70
70
  valid: true,
71
71
  data: payload,
72
+ signingUserFid: header.fid,
72
73
  };
73
74
  }
74
75
  export function decodePayload(payload) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.22.1",
3
+ "version": "2.0.1",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
@@ -53,7 +53,7 @@ export function applyStatePaths(
53
53
  }
54
54
 
55
55
  const CONFETTI_COLORS = [
56
- "#8B5CF6",
56
+ "#907AA9",
57
57
  "#EC4899",
58
58
  "#3B82F6",
59
59
  "#10B981",
@@ -7,7 +7,7 @@ import {
7
7
  } from "react-native";
8
8
 
9
9
  const CONFETTI_COLORS = [
10
- "#8B5CF6",
10
+ "#907AA9",
11
11
  "#EC4899",
12
12
  "#3B82F6",
13
13
  "#10B981",
package/src/schemas.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import type { Spec } from "@json-render/core";
3
- import { EFFECT_VALUES, SUPPORTED_SPEC_VERSIONS, type SpecVersion } from "./constants";
3
+ import {
4
+ EFFECT_VALUES,
5
+ SUPPORTED_SPEC_VERSIONS,
6
+ type SpecVersion,
7
+ } from "./constants";
4
8
  import { DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES } from "./colors";
5
9
 
6
10
  // ─── Theme ─────────────────────────────────────────────
@@ -81,13 +85,35 @@ const postInputValueSchema = z.union([
81
85
  z.array(z.string()),
82
86
  ]);
83
87
 
88
+ const standaloneSurfaceSchema = z.object({
89
+ type: z.literal("standalone"),
90
+ });
91
+
92
+ const castSurfaceSchema = z.object({
93
+ type: z.literal("cast"),
94
+ cast: z.object({
95
+ hash: z.string(),
96
+ author: z.object({
97
+ fid: z.number().int().nonnegative(),
98
+ }),
99
+ }),
100
+ });
101
+
102
+ const surfaceSchema = z.discriminatedUnion("type", [
103
+ castSurfaceSchema,
104
+ standaloneSurfaceSchema,
105
+ ]);
106
+
84
107
  export const payloadSchema = z
85
108
  .object({
86
- fid: z.number().int().nonnegative(),
109
+ fid: z.number().int().nonnegative().optional(), // deprecated in favor of user.fid
87
110
  inputs: z.record(z.string(), postInputValueSchema).default({}),
88
111
  timestamp: z.number().int(),
89
- nonce: z.string(),
90
112
  audience: z.string(),
113
+ user: z.object({
114
+ fid: z.number().int().nonnegative(),
115
+ }),
116
+ surface: surfaceSchema,
91
117
  })
92
118
  .strip();
93
119
 
@@ -33,6 +33,10 @@ export type ParseRequestError =
33
33
  | {
34
34
  type: "origin_mismatch";
35
35
  message: string;
36
+ }
37
+ | {
38
+ type: "fid_mismatch";
39
+ message: string;
36
40
  };
37
41
 
38
42
  export type ParseRequestOptions = {
@@ -52,7 +56,6 @@ export type ParseRequestOptions = {
52
56
  * The origin of the request. Derived from the request when not provided.
53
57
  */
54
58
  requestOrigin?: string;
55
-
56
59
  };
57
60
 
58
61
  export type ParseRequestResult =
@@ -117,16 +120,6 @@ export async function parseRequest(
117
120
  };
118
121
  }
119
122
 
120
- if (!options.skipJFSVerification) {
121
- const jfs = await verifyJFSRequestBody(parsed.data);
122
- if (!jfs.valid) {
123
- return {
124
- success: false,
125
- error: { type: "signature", message: jfs.error.message },
126
- };
127
- }
128
- }
129
-
130
123
  const payloadParsed = payloadSchema.safeParse(
131
124
  decodePayload(parsed.data.payload),
132
125
  );
@@ -138,6 +131,26 @@ export async function parseRequest(
138
131
  }
139
132
 
140
133
  const body = payloadParsed.data;
134
+
135
+ if (!options.skipJFSVerification) {
136
+ const jfs = await verifyJFSRequestBody(parsed.data);
137
+ if (!jfs.valid) {
138
+ return {
139
+ success: false,
140
+ error: { type: "signature", message: jfs.error.message },
141
+ };
142
+ }
143
+ if (jfs.signingUserFid !== body.user.fid) {
144
+ return {
145
+ success: false,
146
+ error: {
147
+ type: "fid_mismatch",
148
+ message: `JFS header fid "${jfs.signingUserFid}" does not match user.fid "${body.user.fid}"`,
149
+ },
150
+ };
151
+ }
152
+ }
153
+
141
154
  if (Math.abs(nowSec - body.timestamp) > maxSkew) {
142
155
  return {
143
156
  success: false,
@@ -173,6 +186,16 @@ export async function parseRequest(
173
186
  };
174
187
  }
175
188
 
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
+
176
199
  return {
177
200
  success: true,
178
201
  action: {
@@ -27,6 +27,7 @@ export async function verifyJFSRequestBody<TPayload>(
27
27
  }
28
28
  | {
29
29
  valid: true;
30
+ signingUserFid: number; // the FID of the user who signed the request
30
31
  data: TPayload;
31
32
  }
32
33
  > {
@@ -108,6 +109,7 @@ export async function verifyJFSRequestBody<TPayload>(
108
109
  return {
109
110
  valid: true,
110
111
  data: payload,
112
+ signingUserFid: header.fid,
111
113
  };
112
114
  }
113
115
 
package/src/ui/README.md CHANGED
@@ -8,14 +8,14 @@ Snaps use a fixed set of named colors called the **palette**:
8
8
 
9
9
  | Name | Light hex | Dark hex |
10
10
  | -------- | --------- | --------- |
11
- | `gray` | `#8F8F8F` | `#8F8F8F` |
12
- | `blue` | `#006BFF` | `#006FFE` |
13
- | `red` | `#FC0036` | `#F13342` |
14
- | `amber` | `#FFAE00` | `#FFAE00` |
15
- | `green` | `#28A948` | `#00AC3A` |
16
- | `teal` | `#00AC96` | `#00AA96` |
17
- | `purple` | `#8B5CF6` | `#A78BFA` |
18
- | `pink` | `#F32782` | `#F12B82` |
11
+ | `gray` | `#6E6A86` | `#908CAA` |
12
+ | `blue` | `#286983` | `#9CCFD8` |
13
+ | `red` | `#B4637A` | `#EB6F92` |
14
+ | `amber` | `#EA9D34` | `#F6C177` |
15
+ | `green` | `#3E8F8F` | `#56D4A4` |
16
+ | `teal` | `#56949F` | `#3E8FB0` |
17
+ | `purple` | `#907AA9` | `#C4A7E7` |
18
+ | `pink` | `#D7827E` | `#EBBCBA` |
19
19
 
20
20
  These are exported from `@farcaster/snap` as `PALETTE_LIGHT_HEX`, `PALETTE_DARK_HEX`, and the `PaletteColor` type. Clients resolve the correct hex for their current light/dark mode.
21
21