@farcaster/miniapp-core 0.0.0-canary-20250630212339

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/actions/AddMiniApp.d.ts +36 -0
  4. package/dist/actions/AddMiniApp.js +57 -0
  5. package/dist/actions/ComposeCast.d.ts +37 -0
  6. package/dist/actions/ComposeCast.js +2 -0
  7. package/dist/actions/Haptics.d.ts +5 -0
  8. package/dist/actions/Haptics.js +2 -0
  9. package/dist/actions/Ready.d.ts +13 -0
  10. package/dist/actions/Ready.js +6 -0
  11. package/dist/actions/SendToken.d.ts +48 -0
  12. package/dist/actions/SendToken.js +2 -0
  13. package/dist/actions/SignIn.d.ts +55 -0
  14. package/dist/actions/SignIn.js +47 -0
  15. package/dist/actions/SwapToken.d.ts +46 -0
  16. package/dist/actions/SwapToken.js +2 -0
  17. package/dist/actions/ViewCast.d.ts +25 -0
  18. package/dist/actions/ViewCast.js +2 -0
  19. package/dist/actions/ViewProfile.d.ts +4 -0
  20. package/dist/actions/ViewProfile.js +2 -0
  21. package/dist/actions/ViewToken.d.ts +4 -0
  22. package/dist/actions/ViewToken.js +2 -0
  23. package/dist/actions/index.d.ts +10 -0
  24. package/dist/actions/index.js +46 -0
  25. package/dist/back.d.ts +13 -0
  26. package/dist/back.js +6 -0
  27. package/dist/context.d.ts +94 -0
  28. package/dist/context.js +2 -0
  29. package/dist/errors.d.ts +13 -0
  30. package/dist/errors.js +12 -0
  31. package/dist/index.d.ts +10 -0
  32. package/dist/index.js +49 -0
  33. package/dist/internal/types.d.ts +8 -0
  34. package/dist/internal/types.js +2 -0
  35. package/dist/manifest.d.ts +113 -0
  36. package/dist/manifest.js +10 -0
  37. package/dist/schemas/embeds.d.ts +128 -0
  38. package/dist/schemas/embeds.js +43 -0
  39. package/dist/schemas/events.d.ts +63 -0
  40. package/dist/schemas/events.js +35 -0
  41. package/dist/schemas/index.d.ts +5 -0
  42. package/dist/schemas/index.js +21 -0
  43. package/dist/schemas/manifest.d.ts +236 -0
  44. package/dist/schemas/manifest.js +127 -0
  45. package/dist/schemas/notifications.d.ts +22 -0
  46. package/dist/schemas/notifications.js +23 -0
  47. package/dist/schemas/shared.d.ts +24 -0
  48. package/dist/schemas/shared.js +110 -0
  49. package/dist/solana.d.ts +72 -0
  50. package/dist/solana.js +15 -0
  51. package/dist/solanaWire.d.ts +3 -0
  52. package/dist/solanaWire.js +84 -0
  53. package/dist/types.d.ts +91 -0
  54. package/dist/types.js +41 -0
  55. package/dist/wallet/ethereum.d.ts +47 -0
  56. package/dist/wallet/ethereum.js +2 -0
  57. package/dist/wallet/index.d.ts +1 -0
  58. package/dist/wallet/index.js +37 -0
  59. package/esm/actions/AddMiniApp.d.ts +36 -0
  60. package/esm/actions/AddMiniApp.js +19 -0
  61. package/esm/actions/ComposeCast.d.ts +37 -0
  62. package/esm/actions/ComposeCast.js +1 -0
  63. package/esm/actions/Haptics.d.ts +5 -0
  64. package/esm/actions/Haptics.js +1 -0
  65. package/esm/actions/Ready.d.ts +13 -0
  66. package/esm/actions/Ready.js +3 -0
  67. package/esm/actions/SendToken.d.ts +48 -0
  68. package/esm/actions/SendToken.js +1 -0
  69. package/esm/actions/SignIn.d.ts +55 -0
  70. package/esm/actions/SignIn.js +10 -0
  71. package/esm/actions/SwapToken.d.ts +46 -0
  72. package/esm/actions/SwapToken.js +1 -0
  73. package/esm/actions/ViewCast.d.ts +25 -0
  74. package/esm/actions/ViewCast.js +1 -0
  75. package/esm/actions/ViewProfile.d.ts +4 -0
  76. package/esm/actions/ViewProfile.js +1 -0
  77. package/esm/actions/ViewToken.d.ts +4 -0
  78. package/esm/actions/ViewToken.js +1 -0
  79. package/esm/actions/index.d.ts +10 -0
  80. package/esm/actions/index.js +10 -0
  81. package/esm/back.d.ts +13 -0
  82. package/esm/back.js +3 -0
  83. package/esm/context.d.ts +94 -0
  84. package/esm/context.js +1 -0
  85. package/esm/errors.d.ts +13 -0
  86. package/esm/errors.js +8 -0
  87. package/esm/index.d.ts +10 -0
  88. package/esm/index.js +10 -0
  89. package/esm/internal/types.d.ts +8 -0
  90. package/esm/internal/types.js +1 -0
  91. package/esm/manifest.d.ts +113 -0
  92. package/esm/manifest.js +7 -0
  93. package/esm/schemas/embeds.d.ts +128 -0
  94. package/esm/schemas/embeds.js +39 -0
  95. package/esm/schemas/events.d.ts +63 -0
  96. package/esm/schemas/events.js +32 -0
  97. package/esm/schemas/index.d.ts +5 -0
  98. package/esm/schemas/index.js +5 -0
  99. package/esm/schemas/manifest.d.ts +236 -0
  100. package/esm/schemas/manifest.js +124 -0
  101. package/esm/schemas/notifications.d.ts +22 -0
  102. package/esm/schemas/notifications.js +20 -0
  103. package/esm/schemas/shared.d.ts +24 -0
  104. package/esm/schemas/shared.js +106 -0
  105. package/esm/solana.d.ts +72 -0
  106. package/esm/solana.js +11 -0
  107. package/esm/solanaWire.d.ts +3 -0
  108. package/esm/solanaWire.js +80 -0
  109. package/esm/tsconfig.tsbuildinfo +1 -0
  110. package/esm/types.d.ts +91 -0
  111. package/esm/types.js +23 -0
  112. package/esm/wallet/ethereum.d.ts +47 -0
  113. package/esm/wallet/ethereum.js +1 -0
  114. package/esm/wallet/index.d.ts +1 -0
  115. package/esm/wallet/index.js +1 -0
  116. package/package.json +42 -0
  117. package/src/actions/AddMiniApp.ts +51 -0
  118. package/src/actions/ComposeCast.ts +44 -0
  119. package/src/actions/Haptics.ts +9 -0
  120. package/src/actions/Ready.ts +15 -0
  121. package/src/actions/SendToken.ts +57 -0
  122. package/src/actions/SignIn.ts +67 -0
  123. package/src/actions/SwapToken.ts +54 -0
  124. package/src/actions/ViewCast.ts +27 -0
  125. package/src/actions/ViewProfile.ts +5 -0
  126. package/src/actions/ViewToken.ts +5 -0
  127. package/src/actions/index.ts +10 -0
  128. package/src/back.ts +15 -0
  129. package/src/context.ts +117 -0
  130. package/src/errors.ts +21 -0
  131. package/src/index.ts +10 -0
  132. package/src/internal/types.ts +20 -0
  133. package/src/manifest.ts +131 -0
  134. package/src/schemas/embeds.ts +58 -0
  135. package/src/schemas/events.ts +57 -0
  136. package/src/schemas/index.ts +5 -0
  137. package/src/schemas/manifest.ts +142 -0
  138. package/src/schemas/notifications.ts +35 -0
  139. package/src/schemas/shared.ts +138 -0
  140. package/src/solana.ts +108 -0
  141. package/src/solanaWire.ts +120 -0
  142. package/src/types.ts +165 -0
  143. package/src/wallet/ethereum.ts +65 -0
  144. package/src/wallet/index.ts +1 -0
@@ -0,0 +1,58 @@
1
+ import { z } from 'zod/v4'
2
+ import {
3
+ aspectRatioSchema,
4
+ buttonTitleSchema,
5
+ caip19TokenSchema,
6
+ hexColorSchema,
7
+ miniAppNameSchema,
8
+ secureUrlSchema,
9
+ } from './shared.ts'
10
+
11
+ export const actionLaunchFrameSchema = z.object({
12
+ type: z.literal('launch_frame'),
13
+ name: miniAppNameSchema,
14
+ url: secureUrlSchema.optional(),
15
+ splashImageUrl: secureUrlSchema.optional(),
16
+ splashBackgroundColor: hexColorSchema.optional(),
17
+ })
18
+
19
+ export const actionLaunchMiniAppSchema = z.object({
20
+ type: z.literal('launch_miniapp'),
21
+ name: miniAppNameSchema,
22
+ url: secureUrlSchema.optional(),
23
+ splashImageUrl: secureUrlSchema.optional(),
24
+ splashBackgroundColor: hexColorSchema.optional(),
25
+ })
26
+
27
+ export const actionViewTokenSchema = z.object({
28
+ type: z.literal('view_token'),
29
+ token: caip19TokenSchema,
30
+ })
31
+
32
+ export const actionSchema = z.discriminatedUnion('type', [
33
+ actionLaunchMiniAppSchema,
34
+ actionViewTokenSchema,
35
+ // Remove after compatibility period
36
+ actionLaunchFrameSchema,
37
+ ])
38
+
39
+ export const buttonSchema = z.object({
40
+ title: buttonTitleSchema,
41
+ action: actionSchema,
42
+ })
43
+
44
+ export const miniAppEmbedNextSchema = z.object({
45
+ version: z.literal('next'),
46
+ imageUrl: secureUrlSchema,
47
+ aspectRatio: aspectRatioSchema.optional(),
48
+ button: buttonSchema,
49
+ })
50
+
51
+ export const safeParseMiniAppEmbed = (rawResponse: unknown) =>
52
+ miniAppEmbedNextSchema.safeParse(rawResponse)
53
+
54
+ // Backward compatibility - also parse fc:frame meta tags
55
+ export const safeParseFrameEmbed = safeParseMiniAppEmbed
56
+
57
+ export type MiniAppEmbedNext = z.infer<typeof miniAppEmbedNextSchema>
58
+ export type FrameEmbedNext = MiniAppEmbedNext
@@ -0,0 +1,57 @@
1
+ import { z } from 'zod/v4'
2
+ import { notificationDetailsSchema } from './notifications.ts'
3
+
4
+ export const eventMiniAppAddedSchema = z.object({
5
+ event: z.literal('miniapp_added'),
6
+ notificationDetails: notificationDetailsSchema.optional(),
7
+ })
8
+
9
+ export type EventMiniAppAdded = z.infer<typeof eventMiniAppAddedSchema>
10
+
11
+ export const eventFrameAddedSchema = z.object({
12
+ event: z.literal('frame_added'),
13
+ notificationDetails: notificationDetailsSchema.optional(),
14
+ })
15
+
16
+ export type EventFrameAdded = z.infer<typeof eventFrameAddedSchema>
17
+
18
+ export const eventMiniAppRemovedSchema = z.object({
19
+ event: z.literal('miniapp_removed'),
20
+ })
21
+
22
+ export type EventMiniAppRemoved = z.infer<typeof eventMiniAppRemovedSchema>
23
+
24
+ export const eventFrameRemovedSchema = z.object({
25
+ event: z.literal('frame_removed'),
26
+ })
27
+
28
+ export type EventFrameRemoved = z.infer<typeof eventFrameRemovedSchema>
29
+
30
+ export const eventNotificationsEnabledSchema = z.object({
31
+ event: z.literal('notifications_enabled'),
32
+ notificationDetails: notificationDetailsSchema.required(),
33
+ })
34
+
35
+ export type EventNotificationsEnabled = z.infer<
36
+ typeof eventNotificationsEnabledSchema
37
+ >
38
+
39
+ export const notificationsDisabledSchema = z.object({
40
+ event: z.literal('notifications_disabled'),
41
+ })
42
+
43
+ export type EventNotificationsDisabled = z.infer<
44
+ typeof notificationsDisabledSchema
45
+ >
46
+
47
+ export const serverEventSchema = z.discriminatedUnion('event', [
48
+ eventMiniAppAddedSchema,
49
+ eventMiniAppRemovedSchema,
50
+ eventNotificationsEnabledSchema,
51
+ notificationsDisabledSchema,
52
+ // Remove after compatibility period
53
+ eventFrameAddedSchema,
54
+ eventFrameRemovedSchema,
55
+ ])
56
+
57
+ export type MiniAppServerEvent = z.infer<typeof serverEventSchema>
@@ -0,0 +1,5 @@
1
+ export * from './embeds.ts'
2
+ export * from './events.ts'
3
+ export * from './shared.ts'
4
+ export * from './manifest.ts'
5
+ export * from './notifications.ts'
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod/v4'
2
+ import { miniAppHostCapabilityList } from '../types.ts'
3
+ import {
4
+ buttonTitleSchema,
5
+ createSimpleStringSchema,
6
+ domainSchema,
7
+ encodedJsonFarcasterSignatureSchema,
8
+ hexColorSchema,
9
+ miniAppNameSchema,
10
+ secureUrlSchema,
11
+ } from './shared.ts'
12
+
13
+ const primaryCategorySchema = z.enum([
14
+ 'games',
15
+ 'social',
16
+ 'finance',
17
+ 'utility',
18
+ 'productivity',
19
+ 'health-fitness',
20
+ 'news-media',
21
+ 'music',
22
+ 'shopping',
23
+ 'education',
24
+ 'developer-tools',
25
+ 'entertainment',
26
+ 'art-creativity',
27
+ ])
28
+
29
+ const chainList = [
30
+ 'eip155:1', // Ethereum mainnet
31
+ 'eip155:8453', // Base mainnet
32
+ 'eip155:42161', // Arbitrum One
33
+ 'eip155:421614', // Arbitrum Sepolia
34
+ 'eip155:84532', // Base Sepolia
35
+ 'eip155:666666666', // Degen
36
+ 'eip155:100', // Gnosis
37
+ 'eip155:10', // Optimism
38
+ 'eip155:11155420', // Optimism Sepolia
39
+ 'eip155:137', // Polygon
40
+ 'eip155:11155111', // Ethereum Sepolia
41
+ 'eip155:7777777', // Zora
42
+ 'eip155:130', // Unichain
43
+ 'eip155:10143', // Monad testnet
44
+ 'eip155:42220', // Celo
45
+ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', // Solana
46
+ ] as const
47
+
48
+ function removeArrayDuplicates<T>(arr: T[]) {
49
+ const set = new Set(arr)
50
+ return Array.from(set)
51
+ }
52
+
53
+ export const domainMiniAppConfigSchema = z
54
+ .object({
55
+ // 0.0.0 and 0.0.1 are not technically part of the spec but kept for
56
+ // backwards compatibility. next should always resolve to the most recent
57
+ // schema version.
58
+ version: z.union([
59
+ z.literal('0.0.0'),
60
+ z.literal('0.0.1'),
61
+ z.literal('1'),
62
+ z.literal('next'),
63
+ ]),
64
+ name: miniAppNameSchema,
65
+ iconUrl: secureUrlSchema,
66
+ homeUrl: secureUrlSchema,
67
+ /** deprecated, set ogImageUrl instead */
68
+ imageUrl: secureUrlSchema.optional(),
69
+ /** deprecated, will rely on fc:frame/fc:miniapp meta tag */
70
+ buttonTitle: buttonTitleSchema.optional(),
71
+ splashImageUrl: secureUrlSchema.optional(),
72
+ splashBackgroundColor: hexColorSchema.optional(),
73
+ webhookUrl: secureUrlSchema.optional(),
74
+ /** see: https://github.com/farcasterxyz/miniapps/discussions/191 */
75
+ subtitle: createSimpleStringSchema({ max: 30 }).optional(),
76
+ description: createSimpleStringSchema({ max: 170 }).optional(),
77
+ screenshotUrls: z.array(secureUrlSchema).max(3).optional(),
78
+ primaryCategory: primaryCategorySchema.optional(),
79
+ tags: z
80
+ .array(createSimpleStringSchema({ max: 20, noSpaces: true }))
81
+ .max(5)
82
+ .optional(),
83
+ heroImageUrl: secureUrlSchema.optional(),
84
+ tagline: createSimpleStringSchema({ max: 30 }).optional(),
85
+ ogTitle: createSimpleStringSchema({ max: 30 }).optional(),
86
+ ogDescription: createSimpleStringSchema({ max: 100 }).optional(),
87
+ ogImageUrl: secureUrlSchema.optional(),
88
+ /** see: https://github.com/farcasterxyz/miniapps/discussions/204 */
89
+ noindex: z.boolean().optional(),
90
+ /** see https://github.com/farcasterxyz/miniapps/discussions/256 */
91
+ requiredChains: z
92
+ .array(z.enum(chainList))
93
+ .transform(removeArrayDuplicates)
94
+ .optional(),
95
+ requiredCapabilities: z
96
+ .array(z.enum(miniAppHostCapabilityList))
97
+ .transform(removeArrayDuplicates)
98
+ .optional(),
99
+ /** see https://github.com/farcasterxyz/miniapps/discussions/158 */
100
+ /** Documentation will be added once this feature is finalized. */
101
+ castShareUrl: secureUrlSchema.optional(),
102
+ /** Canonical domain for the miniapp application */
103
+ canonicalDomain: domainSchema.optional(),
104
+ })
105
+ .refine(
106
+ (data) => {
107
+ if (data.castShareUrl === undefined) return true
108
+ try {
109
+ const homeUrlDomain = new URL(data.homeUrl).hostname
110
+ const castShareUrlDomain = new URL(data.castShareUrl).hostname
111
+ return homeUrlDomain === castShareUrlDomain
112
+ } catch {
113
+ return false
114
+ }
115
+ },
116
+ {
117
+ message: 'castShareUrl must have the same domain as homeUrl',
118
+ path: ['castShareUrl'],
119
+ },
120
+ )
121
+
122
+ export const domainManifestSchema = z
123
+ .object({
124
+ accountAssociation: encodedJsonFarcasterSignatureSchema,
125
+ miniapp: domainMiniAppConfigSchema.optional(),
126
+ // Support both 'frame' and 'miniapp' during transition period
127
+ frame: domainMiniAppConfigSchema.optional(),
128
+ })
129
+ .refine(
130
+ (data) => {
131
+ // If both are provided, they must be identical
132
+ if (data.frame && data.miniapp) {
133
+ return JSON.stringify(data.frame) === JSON.stringify(data.miniapp)
134
+ }
135
+ return true
136
+ },
137
+ {
138
+ message:
139
+ 'If both "frame" and "miniapp" are provided, they must be identical',
140
+ path: ['frame', 'miniapp'],
141
+ },
142
+ )
@@ -0,0 +1,35 @@
1
+ import { z } from 'zod/v4'
2
+ import { secureUrlSchema } from './shared.ts'
3
+
4
+ export const notificationDetailsSchema = z.object({
5
+ url: z.string(),
6
+ token: z.string(),
7
+ })
8
+
9
+ export type MiniAppNotificationDetails = z.infer<
10
+ typeof notificationDetailsSchema
11
+ >
12
+
13
+ export const sendNotificationRequestSchema = z.object({
14
+ notificationId: z.string().max(128),
15
+ title: z.string().max(32),
16
+ body: z.string().max(128),
17
+ targetUrl: secureUrlSchema,
18
+ tokens: z.string().array().max(100),
19
+ })
20
+
21
+ export type SendNotificationRequest = z.infer<
22
+ typeof sendNotificationRequestSchema
23
+ >
24
+
25
+ export const sendNotificationResponseSchema = z.object({
26
+ result: z.object({
27
+ successfulTokens: z.array(z.string()),
28
+ invalidTokens: z.array(z.string()),
29
+ rateLimitedTokens: z.array(z.string()),
30
+ }),
31
+ })
32
+
33
+ export type SendNotificationResponse = z.infer<
34
+ typeof sendNotificationResponseSchema
35
+ >
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod/v4'
2
+
3
+ const SPECIAL_CHARS_PATTERN = /[@#$%^&*+=\/\\|~«»]/
4
+ const REPEATED_PUNCTUATION_PATTERN = /(!{2,}|\?{2,}|-{2,})/
5
+
6
+ // Unicode ranges for emoji detection:
7
+ // \u{1F300}-\u{1F9FF} - Miscellaneous Symbols, Pictographs, Emoticons, Transport, Map, and Supplemental
8
+ // \u{2702}-\u{27B0} - Dingbats
9
+ // \u{2600}-\u{26FF} - Miscellaneous Symbols
10
+ // \u{2B00}-\u{2BFF} - Miscellaneous Symbols and Arrows
11
+ const EMOJI_PATTERN =
12
+ /[\u{1F300}-\u{1F9FF}]|[\u{2702}-\u{27B0}]|[\u{2600}-\u{26FF}]|[\u{2B00}-\u{2BFF}]/u
13
+
14
+ export const createSimpleStringSchema = ({
15
+ max,
16
+ noSpaces,
17
+ }: { max?: number; noSpaces?: boolean } = {}) => {
18
+ const stringValidations = noSpaces
19
+ ? z
20
+ .string()
21
+ .max(max ?? Number.POSITIVE_INFINITY)
22
+ .regex(/^\S*$/, 'Spaces are not allowed')
23
+ : z.string().max(max ?? Number.POSITIVE_INFINITY)
24
+
25
+ return stringValidations
26
+ .refine((value) => !EMOJI_PATTERN.test(value), {
27
+ message: 'Emojis and symbols are not allowed',
28
+ })
29
+ .refine((value) => !SPECIAL_CHARS_PATTERN.test(value), {
30
+ message:
31
+ 'Special characters (@, #, $, %, ^, &, *, +, =, /, \\, |, ~, «, ») are not allowed',
32
+ })
33
+ .refine((value) => !REPEATED_PUNCTUATION_PATTERN.test(value), {
34
+ message: 'Repeated punctuations (!!, ??, --) are not allowed',
35
+ })
36
+ }
37
+
38
+ export const secureUrlSchema = z
39
+ .string()
40
+ .url()
41
+ .startsWith('https://', { message: 'Must be an https url' })
42
+ .max(1024)
43
+ .refine((url) => !url.includes(' '), {
44
+ message: 'URL must not contain spaces',
45
+ })
46
+ .refine(
47
+ (url) => {
48
+ try {
49
+ const hostname = new URL(url).hostname
50
+ // Check for localhost
51
+ if (hostname === 'localhost' || hostname.endsWith('.localhost')) {
52
+ return false
53
+ }
54
+ // Check for IPv4 addresses
55
+ const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/
56
+ if (ipv4Regex.test(hostname)) {
57
+ return false
58
+ }
59
+ // Check for IPv6 addresses (including brackets)
60
+ if (hostname.startsWith('[') && hostname.endsWith(']')) {
61
+ return false
62
+ }
63
+ return true
64
+ } catch {
65
+ return false
66
+ }
67
+ },
68
+ {
69
+ message: 'URL must not use IP addresses or localhost',
70
+ },
71
+ )
72
+
73
+ export const miniAppNameSchema = z.string().max(32)
74
+ export const buttonTitleSchema = z.string().max(32)
75
+
76
+ const CAIP_19_REGEX =
77
+ /^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}\/(?:[-a-z0-9]{3,8}:[-.%a-zA-Z0-9]{1,128}(?:\/[-.%a-zA-Z0-9]{1,78})?|native)$/
78
+
79
+ export const caip19TokenSchema = z
80
+ .string()
81
+ .regex(CAIP_19_REGEX, { message: 'Invalid CAIP-19 asset ID' })
82
+
83
+ export const hexColorSchema = z
84
+ .string()
85
+ .regex(/^#([0-9A-F]{3}|[0-9A-F]{6})$/i, {
86
+ message:
87
+ 'Invalid hex color code. It should be in the format #RRGGBB or #RGB.',
88
+ })
89
+
90
+ // Domain validation regex:
91
+ // - Each label (part between dots) must start and end with alphanumeric
92
+ // - Labels can contain hyphens in the middle
93
+ // - Cannot have consecutive dots
94
+ // - Must have at least one dot (TLD required)
95
+ // - TLD must be at least 2 characters and only letters
96
+ const DOMAIN_REGEX =
97
+ /^(?!.*\.\.)([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/
98
+
99
+ export const domainSchema = z
100
+ .string()
101
+ .max(1024)
102
+ .regex(DOMAIN_REGEX, {
103
+ message: 'Must be a valid domain name (e.g., example.com, sub.example.com)',
104
+ })
105
+ .refine((value) => !value.includes('://'), {
106
+ message: 'Domain must not include protocol (http://, https://, etc.)',
107
+ })
108
+ .refine((value) => !value.includes('/'), {
109
+ message: 'Domain must not include path separators',
110
+ })
111
+ .refine((value) => !value.includes('@'), {
112
+ message: 'Domain must not include @ symbol',
113
+ })
114
+ .refine((value) => !value.includes(':'), {
115
+ message: 'Domain must not include port numbers',
116
+ })
117
+
118
+ export const aspectRatioSchema = z.union([z.literal('1:1'), z.literal('3:2')])
119
+
120
+ export const encodedJsonFarcasterSignatureSchema = z.object({
121
+ header: z.string(),
122
+ payload: z.string(),
123
+ signature: z.string(),
124
+ })
125
+
126
+ export type EncodedJsonFarcasterSignatureSchema = z.infer<
127
+ typeof encodedJsonFarcasterSignatureSchema
128
+ >
129
+
130
+ export const jsonFarcasterSignatureHeaderSchema = z.object({
131
+ fid: z.number(),
132
+ type: z.literal('app_key'),
133
+ key: z.string().startsWith('0x'),
134
+ })
135
+
136
+ export type JsonFarcasterSignatureHeaderSchema = z.infer<
137
+ typeof jsonFarcasterSignatureHeaderSchema
138
+ >
package/src/solana.ts ADDED
@@ -0,0 +1,108 @@
1
+ import {
2
+ Connection as SolanaConnection,
3
+ type SendOptions as SolanaSendOptions,
4
+ type Transaction as SolanaTransaction,
5
+ type VersionedTransaction as SolanaVersionedTransaction,
6
+ } from '@solana/web3.js'
7
+
8
+ export { SolanaConnection }
9
+ export type { SolanaSendOptions }
10
+
11
+ export type SolanaCombinedTransaction =
12
+ | SolanaTransaction
13
+ | SolanaVersionedTransaction
14
+
15
+ export type SolanaConnectRequestArguments = {
16
+ method: 'connect'
17
+ }
18
+ export type SolanaSignMessageRequestArguments = {
19
+ method: 'signMessage'
20
+ params: {
21
+ message: string
22
+ }
23
+ }
24
+ export type SolanaSignAndSendTransactionRequestArguments = {
25
+ method: 'signAndSendTransaction'
26
+ params: {
27
+ transaction: SolanaCombinedTransaction
28
+ options?: SolanaSendOptions
29
+ }
30
+ }
31
+ export type SolanaSignTransactionRequestArguments<
32
+ T extends SolanaCombinedTransaction = SolanaTransaction,
33
+ > = {
34
+ method: 'signTransaction'
35
+ params: {
36
+ transaction: T
37
+ }
38
+ }
39
+
40
+ export type SolanaRequestFn = ((
41
+ request: SolanaConnectRequestArguments,
42
+ ) => Promise<{ publicKey: string }>) &
43
+ ((request: SolanaSignMessageRequestArguments) => Promise<{
44
+ signature: string
45
+ }>) &
46
+ ((request: SolanaSignAndSendTransactionRequestArguments) => Promise<{
47
+ signature: string
48
+ }>) &
49
+ (<T extends SolanaCombinedTransaction>(
50
+ request: SolanaSignTransactionRequestArguments<T>,
51
+ ) => Promise<{ signedTransaction: T }>)
52
+
53
+ export interface SolanaWalletProvider {
54
+ request: SolanaRequestFn
55
+
56
+ signMessage(message: string): Promise<{ signature: string }>
57
+ signTransaction<T extends SolanaCombinedTransaction>(
58
+ transaction: T,
59
+ ): Promise<{ signedTransaction: T }>
60
+ signAndSendTransaction(input: {
61
+ transaction: SolanaCombinedTransaction
62
+ }): Promise<{ signature: string }>
63
+ }
64
+
65
+ export const createSolanaWalletProvider = (
66
+ request: SolanaRequestFn,
67
+ ): SolanaWalletProvider => ({
68
+ request,
69
+ signMessage: (msg: string) =>
70
+ request({ method: 'signMessage', params: { message: msg } }),
71
+ signTransaction: <T extends SolanaCombinedTransaction>(transaction: T) =>
72
+ request({ method: 'signTransaction', params: { transaction } }),
73
+ signAndSendTransaction: (input: {
74
+ transaction: SolanaCombinedTransaction
75
+ }) =>
76
+ request({
77
+ method: 'signAndSendTransaction',
78
+ params: input,
79
+ }),
80
+ })
81
+
82
+ export type SolanaWireSignAndSendTransactionRequestArguments = {
83
+ method: 'signAndSendTransaction'
84
+ params: {
85
+ transaction: string
86
+ options?: SolanaSendOptions
87
+ }
88
+ }
89
+
90
+ export type SolanaWireSignTransactionRequestArguments = {
91
+ method: 'signTransaction'
92
+ params: {
93
+ transaction: string
94
+ }
95
+ }
96
+
97
+ export type SolanaWireRequestFn = ((
98
+ request: SolanaConnectRequestArguments,
99
+ ) => Promise<{ publicKey: string }>) &
100
+ ((request: SolanaSignMessageRequestArguments) => Promise<{
101
+ signature: string
102
+ }>) &
103
+ ((request: SolanaWireSignAndSendTransactionRequestArguments) => Promise<{
104
+ signature: string
105
+ }>) &
106
+ ((
107
+ request: SolanaWireSignTransactionRequestArguments,
108
+ ) => Promise<{ signedTransaction: string }>)
@@ -0,0 +1,120 @@
1
+ import {
2
+ Transaction as SolanaTransaction,
3
+ VersionedMessage as SolanaVersionedMessage,
4
+ VersionedTransaction as SolanaVersionedTransaction,
5
+ } from '@solana/web3.js'
6
+
7
+ import type {
8
+ SolanaCombinedTransaction,
9
+ SolanaConnectRequestArguments,
10
+ SolanaRequestFn,
11
+ SolanaSignAndSendTransactionRequestArguments,
12
+ SolanaSignMessageRequestArguments,
13
+ SolanaSignTransactionRequestArguments,
14
+ SolanaWireRequestFn,
15
+ SolanaWireSignAndSendTransactionRequestArguments,
16
+ SolanaWireSignTransactionRequestArguments,
17
+ } from './solana.ts'
18
+
19
+ function serializeTransaction(transaction: SolanaCombinedTransaction): string {
20
+ return Buffer.from(
21
+ transaction.serialize({
22
+ verifySignatures: false,
23
+ }),
24
+ ).toString('base64')
25
+ }
26
+
27
+ function unserializeTransaction(
28
+ transaction: string,
29
+ ): SolanaCombinedTransaction {
30
+ const bytes = Buffer.from(transaction, 'base64')
31
+ const version = SolanaVersionedMessage.deserializeMessageVersion(bytes)
32
+ return version === 'legacy'
33
+ ? SolanaVersionedTransaction.deserialize(bytes)
34
+ : SolanaTransaction.from(bytes)
35
+ }
36
+
37
+ export function wrapSolanaProviderRequest(
38
+ requestFn: SolanaRequestFn,
39
+ ): SolanaWireRequestFn {
40
+ const wrappedFn = async (
41
+ request:
42
+ | SolanaConnectRequestArguments
43
+ | SolanaSignMessageRequestArguments
44
+ | SolanaWireSignAndSendTransactionRequestArguments
45
+ | SolanaWireSignTransactionRequestArguments,
46
+ ) => {
47
+ if (request.method === 'connect') {
48
+ return await requestFn(request)
49
+ }
50
+ if (request.method === 'signMessage') {
51
+ return await requestFn(request)
52
+ }
53
+ if (request.method === 'signAndSendTransaction') {
54
+ const { transaction, options } = request.params
55
+ const params = {
56
+ transaction: unserializeTransaction(transaction),
57
+ options,
58
+ }
59
+ return await requestFn({
60
+ method: 'signAndSendTransaction',
61
+ params,
62
+ })
63
+ }
64
+ if (request.method === 'signTransaction') {
65
+ const { transaction } = request.params
66
+ const params = {
67
+ transaction: unserializeTransaction(transaction),
68
+ }
69
+ const { signedTransaction } = await requestFn({
70
+ method: 'signTransaction',
71
+ params,
72
+ })
73
+ return {
74
+ signedTransaction: serializeTransaction(signedTransaction),
75
+ }
76
+ }
77
+ }
78
+ return wrappedFn as SolanaWireRequestFn
79
+ }
80
+
81
+ export function unwrapSolanaProviderRequest(
82
+ wrappedRequestFn: SolanaWireRequestFn,
83
+ ): SolanaRequestFn {
84
+ const unwrappedFn = async <T extends SolanaCombinedTransaction>(
85
+ request:
86
+ | SolanaConnectRequestArguments
87
+ | SolanaSignMessageRequestArguments
88
+ | SolanaSignAndSendTransactionRequestArguments
89
+ | SolanaSignTransactionRequestArguments<T>,
90
+ ) => {
91
+ if (request.method === 'connect') {
92
+ return await wrappedRequestFn(request)
93
+ }
94
+ if (request.method === 'signMessage') {
95
+ return await wrappedRequestFn(request)
96
+ }
97
+ if (request.method === 'signAndSendTransaction') {
98
+ const { transaction, options } = request.params
99
+ const params = {
100
+ transaction: serializeTransaction(transaction),
101
+ }
102
+ return await wrappedRequestFn({
103
+ method: 'signAndSendTransaction',
104
+ params,
105
+ })
106
+ }
107
+ if (request.method === 'signTransaction') {
108
+ const { transaction } = request.params
109
+ const params = {
110
+ transaction: serializeTransaction(transaction),
111
+ }
112
+ const { signedTransaction } = await wrappedRequestFn({
113
+ method: 'signTransaction',
114
+ params,
115
+ })
116
+ return { signedTransaction: unserializeTransaction(signedTransaction) }
117
+ }
118
+ }
119
+ return unwrappedFn as SolanaRequestFn
120
+ }