@farcaster/snap 1.4.1 → 1.5.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/dist/schemas.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- import { BAR_CHART_COLOR_VALUES, BUTTON_ACTION, BUTTON_ACTION_VALUES, BUTTON_GROUP_STYLE, BUTTON_GROUP_STYLE_VALUES, BUTTON_LAYOUT_VALUES, BUTTON_STYLE_VALUES, DEFAULT_BUTTON_LAYOUT, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, DEFAULT_THEME_ACCENT, EFFECT_VALUES, ELEMENT_TYPE, GRID_CELL_SIZE_VALUES, GRID_GAP_VALUES, GROUP_LAYOUT_VALUES, HEX_COLOR_6_RE, HTTP_PREFIX, HTTPS_PREFIX, IMAGE_ASPECT_VALUES, INTERACTIVE_ELEMENT_TYPES, LIMITS, LIST_STYLE_VALUES, MEDIA_ELEMENT_TYPES, PAGE_ROOT_TYPE, PALETTE_COLOR_VALUES, PROGRESS_COLOR_VALUES, SLIDER_STEP_ALIGN_EPS, SPACER_SIZE, SPACER_SIZE_VALUES, SPEC_VERSION, TEXT_ALIGN_VALUES, TEXT_CONTENT_MAX, TEXT_STYLE, TEXT_STYLE_VALUES, } from "./constants.js";
2
+ import { BUTTON_ACTION, BUTTON_ACTION_VALUES, CLIENT_ACTION, BUTTON_GROUP_STYLE, BUTTON_GROUP_STYLE_VALUES, BUTTON_LAYOUT_VALUES, BUTTON_STYLE_VALUES, DEFAULT_BUTTON_LAYOUT, DEFAULT_GRID_GAP, DEFAULT_LIST_STYLE, DEFAULT_SLIDER_STEP, EFFECT_VALUES, ELEMENT_TYPE, GRID_CELL_SIZE_VALUES, GRID_GAP_VALUES, GROUP_LAYOUT_VALUES, HEX_COLOR_6_RE, HTTP_PREFIX, HTTPS_PREFIX, IMAGE_ASPECT_VALUES, INTERACTIVE_ELEMENT_TYPES, LIMITS, LIST_STYLE_VALUES, MEDIA_ELEMENT_TYPES, PAGE_ROOT_TYPE, SLIDER_STEP_ALIGN_EPS, SPACER_SIZE, SPACER_SIZE_VALUES, SPEC_VERSION, TEXT_ALIGN_VALUES, TEXT_CONTENT_MAX, TEXT_STYLE, TEXT_STYLE_VALUES, } from "./constants.js";
3
+ import { BAR_CHART_COLOR_VALUES, DEFAULT_THEME_ACCENT, PALETTE_COLOR_VALUES, PROGRESS_COLOR_VALUES, } from "./colors.js";
3
4
  /**
4
5
  * post/link/mini_app targets must be HTTPS in production; allow HTTP only for
5
6
  * loopback hosts so local snap servers (e.g. http://localhost:3014/snap) validate.
@@ -28,25 +29,8 @@ const themeSchema = z
28
29
  accent: themeAccentSchema.default(DEFAULT_THEME_ACCENT),
29
30
  })
30
31
  .strict();
31
- const httpsUrl = z.string().refine((s) => s.startsWith(HTTPS_PREFIX), {
32
- message: "URL must use HTTPS",
33
- });
34
- function hasAllowedMediaExtension(urlString, allowedExtensions) {
35
- try {
36
- const url = new URL(urlString);
37
- if (url.protocol !== "https:")
38
- return false;
39
- const lowerPathname = url.pathname.toLowerCase();
40
- return allowedExtensions.some((extension) => lowerPathname.endsWith(`.${extension}`));
41
- }
42
- catch {
43
- return false;
44
- }
45
- }
46
- const imageUrlSchema = z
47
- .string()
48
- .refine((s) => hasAllowedMediaExtension(s, ["jpg", "png", "gif", "webp"]), {
49
- message: "image URL must use HTTPS and end with a supported extension (.jpg, .png, .gif, .webp)",
32
+ const imageUrlSchema = z.string().refine((s) => s.startsWith(HTTPS_PREFIX), {
33
+ message: "image URL must use HTTPS",
50
34
  });
51
35
  const textAlignSchema = z.enum(TEXT_ALIGN_VALUES);
52
36
  const textElementSchema = z
@@ -152,7 +136,7 @@ const gridElementSchema = z
152
136
  rows: z.number().int().min(LIMITS.minGridRows).max(LIMITS.maxGridRows),
153
137
  cells: z.array(gridCellSchema),
154
138
  cellSize: z.enum(GRID_CELL_SIZE_VALUES).optional(),
155
- gap: z.enum(GRID_GAP_VALUES).optional(),
139
+ gap: z.enum(GRID_GAP_VALUES).default(DEFAULT_GRID_GAP),
156
140
  interactive: z.boolean().optional(),
157
141
  })
158
142
  .superRefine((val, ctx) => {
@@ -300,24 +284,134 @@ const barChartElementSchema = z
300
284
  });
301
285
  const buttonActionSchema = z.enum(BUTTON_ACTION_VALUES);
302
286
  const buttonStyleSchema = z.enum(BUTTON_STYLE_VALUES);
287
+ /* ------------------------------------------------------------------ */
288
+ /* Client action schemas */
289
+ /* ------------------------------------------------------------------ */
290
+ const viewCastClientActionSchema = z
291
+ .object({
292
+ type: z.literal(CLIENT_ACTION.view_cast),
293
+ hash: z.string().min(1),
294
+ })
295
+ .strict();
296
+ const viewProfileClientActionSchema = z
297
+ .object({
298
+ type: z.literal(CLIENT_ACTION.view_profile),
299
+ fid: z.number().int().nonnegative(),
300
+ })
301
+ .strict();
302
+ const composeCastClientActionSchema = z
303
+ .object({
304
+ type: z.literal(CLIENT_ACTION.compose_cast),
305
+ text: z.string().optional(),
306
+ embeds: z
307
+ .array(z.string())
308
+ .max(2, { message: "compose_cast embeds: max 2 URLs" })
309
+ .optional(),
310
+ parent: z
311
+ .object({
312
+ type: z.literal("cast"),
313
+ hash: z.string().min(1),
314
+ })
315
+ .strict()
316
+ .optional(),
317
+ channelKey: z.string().optional(),
318
+ })
319
+ .strict();
320
+ const viewTokenClientActionSchema = z
321
+ .object({
322
+ type: z.literal(CLIENT_ACTION.view_token),
323
+ /** CAIP-19 asset ID (e.g. "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913") */
324
+ token: z.string().min(1),
325
+ })
326
+ .strict();
327
+ const sendTokenClientActionSchema = z
328
+ .object({
329
+ type: z.literal(CLIENT_ACTION.send_token),
330
+ /** CAIP-19 asset ID */
331
+ token: z.string().optional(),
332
+ /** Amount in raw token units (e.g. "1000000" for 1 USDC) */
333
+ amount: z.string().optional(),
334
+ recipientFid: z.number().int().nonnegative().optional(),
335
+ recipientAddress: z.string().optional(),
336
+ })
337
+ .strict();
338
+ const swapTokenClientActionSchema = z
339
+ .object({
340
+ type: z.literal(CLIENT_ACTION.swap_token),
341
+ /** CAIP-19 asset ID to sell */
342
+ sellToken: z.string().optional(),
343
+ /** CAIP-19 asset ID to buy */
344
+ buyToken: z.string().optional(),
345
+ /** Amount in raw token units */
346
+ sellAmount: z.string().optional(),
347
+ })
348
+ .strict();
349
+ export const clientActionSchema = z.discriminatedUnion("type", [
350
+ viewCastClientActionSchema,
351
+ viewProfileClientActionSchema,
352
+ composeCastClientActionSchema,
353
+ viewTokenClientActionSchema,
354
+ sendTokenClientActionSchema,
355
+ swapTokenClientActionSchema,
356
+ ]);
357
+ /* ------------------------------------------------------------------ */
358
+ /* Button schema */
359
+ /* ------------------------------------------------------------------ */
303
360
  const buttonSchema = z
304
361
  .object({
305
362
  label: z.string().min(1).max(LIMITS.maxButtonLabelChars),
306
363
  action: buttonActionSchema,
307
- /** URL (HTTPS for post/link/mini_app) or SDK action id (e.g. cast:view:...) */
308
- target: z.string().min(1),
364
+ /** URL target for post/link/mini_app buttons */
365
+ target: z.string().min(1).optional(),
366
+ /** Structured client action for client buttons */
367
+ client_action: clientActionSchema.optional(),
309
368
  style: buttonStyleSchema.optional(),
310
369
  })
311
370
  .superRefine((val, ctx) => {
312
- if ((val.action === BUTTON_ACTION.post ||
313
- val.action === BUTTON_ACTION.link ||
314
- val.action === BUTTON_ACTION.mini_app) &&
315
- !isSecureOrLoopbackHttpButtonTarget(val.target)) {
316
- ctx.addIssue({
317
- code: "custom",
318
- message: `button target must use HTTPS (or http:// on localhost / 127.0.0.1 for development) for action "${val.action}" (received: ${val.target})`,
319
- path: ["target"],
320
- });
371
+ if (val.action === BUTTON_ACTION.client) {
372
+ // client buttons require client_action, must not have target
373
+ if (val.client_action === undefined) {
374
+ ctx.addIssue({
375
+ code: "custom",
376
+ message: `button with action "client" must include a "client_action" object`,
377
+ path: ["client_action"],
378
+ });
379
+ }
380
+ if (val.target !== undefined) {
381
+ ctx.addIssue({
382
+ code: "custom",
383
+ message: `button with action "client" must not include "target"`,
384
+ path: ["target"],
385
+ });
386
+ }
387
+ }
388
+ else {
389
+ // post/link/mini_app buttons require target, must not have client_action
390
+ if (val.target === undefined) {
391
+ ctx.addIssue({
392
+ code: "custom",
393
+ message: `button with action "${val.action}" must include a "target" URL`,
394
+ path: ["target"],
395
+ });
396
+ }
397
+ if (val.client_action !== undefined) {
398
+ ctx.addIssue({
399
+ code: "custom",
400
+ message: `button with action "${val.action}" must not include "client_action"`,
401
+ path: ["client_action"],
402
+ });
403
+ }
404
+ if (val.target &&
405
+ (val.action === BUTTON_ACTION.post ||
406
+ val.action === BUTTON_ACTION.link ||
407
+ val.action === BUTTON_ACTION.mini_app) &&
408
+ !isSecureOrLoopbackHttpButtonTarget(val.target)) {
409
+ ctx.addIssue({
410
+ code: "custom",
411
+ message: `button target must use HTTPS (or http:// on localhost / 127.0.0.1 for development) for action "${val.action}" (received: ${val.target})`,
412
+ path: ["target"],
413
+ });
414
+ }
321
415
  }
322
416
  });
323
417
  /** Child elements allowed inside `group` (no media, no nested group) */
@@ -457,3 +551,17 @@ export const snapActionSchema = z.discriminatedUnion("type", [
457
551
  snapGetActionSchema,
458
552
  snapPostActionSchema,
459
553
  ]);
554
+ export function createDefaultDataStore() {
555
+ const err = new Error("Data store is not configured. Use withUpstash() from @farcaster/snap-upstash or provide a data store implementation.");
556
+ return {
557
+ get(_key) {
558
+ return Promise.reject(err);
559
+ },
560
+ set(_key, _value) {
561
+ return Promise.reject(err);
562
+ },
563
+ withLock(_fn) {
564
+ return Promise.reject(err);
565
+ },
566
+ };
567
+ }
@@ -21,6 +21,12 @@ export type ParseRequestOptions = {
21
21
  * When true, skip {@link verifyJFSRequestBody} (signature checks).
22
22
  */
23
23
  skipJFSVerification?: boolean;
24
+ /**
25
+ * Maximum allowed absolute difference between the request timestamp and the
26
+ * server clock, in seconds. Requests outside this window are rejected as
27
+ * potential replays. Defaults to 300 (5 minutes) when not provided.
28
+ */
29
+ maxSkewSeconds?: number;
24
30
  };
25
31
  export type ParseRequestResult = {
26
32
  success: true;
@@ -1,7 +1,6 @@
1
1
  import { ACTION_TYPE_GET, ACTION_TYPE_POST, payloadSchema, } from "../schemas.js";
2
2
  import { decodePayload, verifyJFSRequestBody } from "./verify.js";
3
3
  import { z } from "zod";
4
- /** Default replay window per SPEC.md § Replay Protection (5 minutes). */
5
4
  const DEFAULT_SNAP_POST_MAX_SKEW_SECONDS = 300;
6
5
  const requestBodySchema = z.object({
7
6
  header: z.string(),
@@ -29,7 +28,7 @@ export async function parseRequest(request, options = {}) {
29
28
  action: { type: ACTION_TYPE_GET },
30
29
  };
31
30
  }
32
- const maxSkew = DEFAULT_SNAP_POST_MAX_SKEW_SECONDS;
31
+ const maxSkew = options.maxSkewSeconds ?? DEFAULT_SNAP_POST_MAX_SKEW_SECONDS;
33
32
  const nowSec = Math.floor(Date.now() / 1000);
34
33
  const text = await request.text();
35
34
  let jsonBody;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
- import { BAR_CHART_COLOR_VALUES, LIMITS, PALETTE_COLOR_VALUES, } from "../constants.js";
2
+ import { LIMITS } from "../constants.js";
3
+ import { BAR_CHART_COLOR_VALUES, PALETTE_COLOR_VALUES } from "../colors.js";
3
4
  export const barChartProps = z.object({
4
5
  bars: z
5
6
  .array(z.object({
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- export declare const buttonGroupProps: z.ZodObject<{
2
+ export declare const buttonGroupProps: z.ZodPipe<z.ZodObject<{
3
3
  name: z.ZodString;
4
4
  options: z.ZodArray<z.ZodString>;
5
5
  style: z.ZodOptional<z.ZodEnum<{
@@ -7,5 +7,13 @@ export declare const buttonGroupProps: z.ZodObject<{
7
7
  stack: "stack";
8
8
  grid: "grid";
9
9
  }>>;
10
- }, z.core.$strip>;
10
+ }, z.core.$strip>, z.ZodTransform<{
11
+ style: "row" | "stack" | "grid";
12
+ name: string;
13
+ options: string[];
14
+ }, {
15
+ name: string;
16
+ options: string[];
17
+ style?: "row" | "stack" | "grid" | undefined;
18
+ }>>;
11
19
  export type ButtonGroupProps = z.infer<typeof buttonGroupProps>;
@@ -1,10 +1,18 @@
1
1
  import { z } from "zod";
2
- import { BUTTON_GROUP_STYLE_VALUES, LIMITS } from "../constants.js";
3
- export const buttonGroupProps = z.object({
2
+ import { BUTTON_GROUP_STYLE, BUTTON_GROUP_STYLE_VALUES, LIMITS, } from "../constants.js";
3
+ export const buttonGroupProps = z
4
+ .object({
4
5
  name: z.string().min(1),
5
6
  options: z
6
- .array(z.string())
7
+ .array(z.string().max(LIMITS.maxButtonGroupOptionChars))
7
8
  .min(LIMITS.minButtonGroupOptions)
8
9
  .max(LIMITS.maxButtonGroupOptions),
9
10
  style: z.enum(BUTTON_GROUP_STYLE_VALUES).optional(),
10
- });
11
+ })
12
+ .transform((val) => ({
13
+ ...val,
14
+ style: val.style ??
15
+ (val.options.length <= 3
16
+ ? BUTTON_GROUP_STYLE.row
17
+ : BUTTON_GROUP_STYLE.stack),
18
+ }));
@@ -5,9 +5,10 @@ export declare const actionButtonProps: z.ZodObject<{
5
5
  post: "post";
6
6
  link: "link";
7
7
  mini_app: "mini_app";
8
- sdk: "sdk";
8
+ client: "client";
9
9
  }>;
10
- target: z.ZodString;
10
+ target: z.ZodOptional<z.ZodString>;
11
+ client_action: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
11
12
  style: z.ZodOptional<z.ZodEnum<{
12
13
  primary: "primary";
13
14
  secondary: "secondary";
@@ -21,9 +22,10 @@ export declare const buttonProps: z.ZodObject<{
21
22
  post: "post";
22
23
  link: "link";
23
24
  mini_app: "mini_app";
24
- sdk: "sdk";
25
+ client: "client";
25
26
  }>;
26
- target: z.ZodString;
27
+ target: z.ZodOptional<z.ZodString>;
28
+ client_action: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
27
29
  style: z.ZodOptional<z.ZodEnum<{
28
30
  primary: "primary";
29
31
  secondary: "secondary";
package/dist/ui/button.js CHANGED
@@ -3,7 +3,8 @@ import { BUTTON_ACTION_VALUES, BUTTON_STYLE_VALUES } from "../constants.js";
3
3
  export const actionButtonProps = z.object({
4
4
  label: z.string(),
5
5
  action: z.enum(BUTTON_ACTION_VALUES),
6
- target: z.string(),
6
+ target: z.string().optional(),
7
+ client_action: z.record(z.string(), z.unknown()).optional(),
7
8
  style: z.enum(BUTTON_STYLE_VALUES).optional(),
8
9
  });
9
10
  /** Same schema as `actionButtonProps` (legacy export name). */
@@ -69,7 +69,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
69
69
  };
70
70
  Spacer: {
71
71
  props: z.ZodObject<{
72
- size: z.ZodOptional<z.ZodEnum<{
72
+ size: z.ZodDefault<z.ZodEnum<{
73
73
  small: "small";
74
74
  medium: "medium";
75
75
  large: "large";
@@ -155,7 +155,7 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
155
155
  description: string;
156
156
  };
157
157
  ButtonGroup: {
158
- props: z.ZodObject<{
158
+ props: z.ZodPipe<z.ZodObject<{
159
159
  name: z.ZodString;
160
160
  options: z.ZodArray<z.ZodString>;
161
161
  style: z.ZodOptional<z.ZodEnum<{
@@ -163,14 +163,22 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
163
163
  stack: "stack";
164
164
  grid: "grid";
165
165
  }>>;
166
- }, z.core.$strip>;
166
+ }, z.core.$strip>, z.ZodTransform<{
167
+ style: "row" | "stack" | "grid";
168
+ name: string;
169
+ options: string[];
170
+ }, {
171
+ name: string;
172
+ options: string[];
173
+ style?: "row" | "stack" | "grid" | undefined;
174
+ }>>;
167
175
  description: string;
168
176
  };
169
177
  Toggle: {
170
178
  props: z.ZodObject<{
171
179
  name: z.ZodString;
172
180
  label: z.ZodString;
173
- value: z.ZodOptional<z.ZodBoolean>;
181
+ value: z.ZodDefault<z.ZodBoolean>;
174
182
  }, z.core.$strip>;
175
183
  description: string;
176
184
  };
@@ -209,7 +217,6 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
209
217
  props: z.ZodObject<{
210
218
  layout: z.ZodEnum<{
211
219
  row: "row";
212
- grid: "grid";
213
220
  }>;
214
221
  }, z.core.$strip>;
215
222
  description: string;
@@ -225,9 +232,10 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
225
232
  post: "post";
226
233
  link: "link";
227
234
  mini_app: "mini_app";
228
- sdk: "sdk";
235
+ client: "client";
229
236
  }>;
230
- target: z.ZodString;
237
+ target: z.ZodOptional<z.ZodString>;
238
+ client_action: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
231
239
  style: z.ZodOptional<z.ZodEnum<{
232
240
  primary: "primary";
233
241
  secondary: "secondary";
@@ -261,10 +269,10 @@ export declare const snapJsonRenderCatalog: import("@json-render/core").Catalog<
261
269
  target: z.ZodString;
262
270
  }, z.core.$strip>;
263
271
  };
264
- snap_sdk: {
272
+ snap_client: {
265
273
  description: string;
266
274
  params: z.ZodObject<{
267
- target: z.ZodString;
275
+ client_action: z.ZodRecord<z.ZodString, z.ZodUnknown>;
268
276
  }, z.core.$strip>;
269
277
  };
270
278
  };
@@ -26,6 +26,9 @@ const snapPostParams = z.object({
26
26
  const snapTargetParams = z.object({
27
27
  target: z.string(),
28
28
  });
29
+ const snapClientParams = z.object({
30
+ client_action: z.record(z.string(), z.unknown()),
31
+ });
29
32
  /**
30
33
  * Basic catalog: one json-render component per snap element type, plus ActionButton for snap buttons.
31
34
  * Does not validate cross-field rules (media count, height budget); snap JSON still goes through `@farcaster/snap` validation.
@@ -90,7 +93,7 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
90
93
  },
91
94
  ActionButton: {
92
95
  props: actionButtonProps,
93
- description: "Snap action button: post (next page), link (browser), mini_app, sdk — target is HTTPS URL or SDK id.",
96
+ description: "Snap action button: post (next page), link (browser), mini_app, client — target is HTTPS URL or client_action object.",
94
97
  },
95
98
  },
96
99
  actions: {
@@ -106,9 +109,9 @@ export const snapJsonRenderCatalog = defineCatalog(snapJsonRenderSchema, {
106
109
  description: "Open `target` as an in-app Farcaster mini app.",
107
110
  params: snapTargetParams,
108
111
  },
109
- snap_sdk: {
110
- description: "Run a Farcaster client SDK action (cast:view, user:follow, …).",
111
- params: snapTargetParams,
112
+ snap_client: {
113
+ description: "Trigger a Farcaster client action (view_cast, view_profile, compose_cast, …).",
114
+ params: snapClientParams,
112
115
  },
113
116
  },
114
117
  });
@@ -2,7 +2,6 @@ import { z } from "zod";
2
2
  export declare const groupProps: z.ZodObject<{
3
3
  layout: z.ZodEnum<{
4
4
  row: "row";
5
- grid: "grid";
6
5
  }>;
7
6
  }, z.core.$strip>;
8
7
  export type GroupProps = z.infer<typeof groupProps>;
package/dist/ui/group.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { GROUP_LAYOUT_VALUES } from "../constants.js";
2
3
  export const groupProps = z.object({
3
- layout: z.enum(["row", "grid"]),
4
+ layout: z.enum(GROUP_LAYOUT_VALUES),
4
5
  });
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { PROGRESS_COLOR_VALUES } from "../constants.js";
2
+ import { PROGRESS_COLOR_VALUES } from "../colors.js";
3
3
  export const progressProps = z.object({
4
4
  value: z.number(),
5
5
  max: z.number(),
package/dist/ui/schema.js CHANGED
@@ -26,6 +26,6 @@ export const snapJsonRenderSchema = defineSchema((s) => ({
26
26
  defaultRules: [
27
27
  "You are generating auxiliary UI for a Farcaster Snap. Prefer components matching snap element types (Text, Image, ButtonGroup, …).",
28
28
  "Snap pages use a Stack root with at most 5 body children and 1 media element (Image or Grid); keep generated trees small.",
29
- "Bottom-of-card snap buttons are ActionButton components; use actions post / link / mini_app / sdk per SPEC.md.",
29
+ "Bottom-of-card snap buttons are ActionButton components; use actions post / link / mini_app / client.",
30
30
  ],
31
31
  });
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  export declare const spacerProps: z.ZodObject<{
3
- size: z.ZodOptional<z.ZodEnum<{
3
+ size: z.ZodDefault<z.ZodEnum<{
4
4
  small: "small";
5
5
  medium: "medium";
6
6
  large: "large";
package/dist/ui/spacer.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { SPACER_SIZE_VALUES } from "../constants.js";
2
+ import { SPACER_SIZE, SPACER_SIZE_VALUES } from "../constants.js";
3
3
  export const spacerProps = z.object({
4
- size: z.enum(SPACER_SIZE_VALUES).optional(),
4
+ size: z.enum(SPACER_SIZE_VALUES).default(SPACER_SIZE.medium),
5
5
  });
@@ -2,6 +2,6 @@ import { z } from "zod";
2
2
  export declare const toggleProps: z.ZodObject<{
3
3
  name: z.ZodString;
4
4
  label: z.ZodString;
5
- value: z.ZodOptional<z.ZodBoolean>;
5
+ value: z.ZodDefault<z.ZodBoolean>;
6
6
  }, z.core.$strip>;
7
7
  export type ToggleProps = z.infer<typeof toggleProps>;
package/dist/ui/toggle.js CHANGED
@@ -2,5 +2,5 @@ import { z } from "zod";
2
2
  export const toggleProps = z.object({
3
3
  name: z.string().min(1),
4
4
  label: z.string(),
5
- value: z.boolean().optional(),
5
+ value: z.boolean().default(false),
6
6
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farcaster/snap",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "Farcaster Snaps 🫰",
5
5
  "repository": {
6
6
  "type": "git",
package/src/colors.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Named color palette for snaps. Snap authors specify a name; the client maps
3
+ * it to a hex value appropriate for its current light/dark mode.
4
+ *
5
+ * Light-mode hex values (used by emulator):
6
+ * gray=#8F8F8F blue=#006BFF red=#FC0036 amber=#FFAE00
7
+ * green=#28A948 teal=#00AC96 purple=#8B5CF6 pink=#F32782
8
+ *
9
+ * Dark-mode hex values (for reference; client-owned):
10
+ * gray=#8F8F8F blue=#006FFE red=#F13342 amber=#FFAE00
11
+ * green=#00AC3A teal=#00AA96 purple=#A78BFA pink=#F12B82
12
+ */
13
+ export const PALETTE_COLOR = {
14
+ gray: "gray",
15
+ blue: "blue",
16
+ red: "red",
17
+ amber: "amber",
18
+ green: "green",
19
+ teal: "teal",
20
+ purple: "purple",
21
+ pink: "pink",
22
+ } as const;
23
+
24
+ export const PALETTE_COLOR_ACCENT = "accent" as const;
25
+
26
+ export const DEFAULT_THEME_ACCENT = PALETTE_COLOR.purple;
27
+
28
+ export const PALETTE_COLOR_VALUES = [
29
+ PALETTE_COLOR.gray,
30
+ PALETTE_COLOR.blue,
31
+ PALETTE_COLOR.red,
32
+ PALETTE_COLOR.amber,
33
+ PALETTE_COLOR.green,
34
+ PALETTE_COLOR.teal,
35
+ PALETTE_COLOR.purple,
36
+ PALETTE_COLOR.pink,
37
+ ] as const;
38
+
39
+ export type PaletteColor = (typeof PALETTE_COLOR_VALUES)[number];
40
+
41
+ /** Light-mode hex for each palette color (emulator / reference client). */
42
+ export const PALETTE_LIGHT_HEX: Record<PaletteColor, string> = {
43
+ gray: "#8F8F8F",
44
+ blue: "#006BFF",
45
+ red: "#FC0036",
46
+ amber: "#FFAE00",
47
+ green: "#28A948",
48
+ teal: "#00AC96",
49
+ purple: "#8B5CF6",
50
+ pink: "#F32782",
51
+ };
52
+
53
+ /** Dark-mode hex for each palette color (reference). */
54
+ export const PALETTE_DARK_HEX: Record<PaletteColor, string> = {
55
+ gray: "#8F8F8F",
56
+ blue: "#006FFE",
57
+ red: "#F13342",
58
+ amber: "#FFAE00",
59
+ green: "#00AC3A",
60
+ teal: "#00AA96",
61
+ purple: "#A78BFA",
62
+ pink: "#F12B82",
63
+ };
64
+
65
+ export const PROGRESS_COLOR_VALUES = [
66
+ PALETTE_COLOR_ACCENT,
67
+ ...PALETTE_COLOR_VALUES,
68
+ ] as const;
69
+
70
+ export const BAR_CHART_COLOR_VALUES = [
71
+ PALETTE_COLOR_ACCENT,
72
+ ...PALETTE_COLOR_VALUES,
73
+ ] as const;