@farcaster/frame-node 0.0.41 → 0.1.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/src/farcaster.ts DELETED
@@ -1,86 +0,0 @@
1
- import * as AbiParameters from 'ox/AbiParameters'
2
- import { z } from 'zod'
3
- import {
4
- BaseError,
5
- type VerifyAppKey,
6
- type VerifyAppKeyResult,
7
- } from './types.ts'
8
-
9
- export const signedKeyRequestAbi = [
10
- {
11
- components: [
12
- {
13
- name: 'requestFid',
14
- type: 'uint256',
15
- },
16
- {
17
- name: 'requestSigner',
18
- type: 'address',
19
- },
20
- {
21
- name: 'signature',
22
- type: 'bytes',
23
- },
24
- {
25
- name: 'deadline',
26
- type: 'uint256',
27
- },
28
- ],
29
- name: 'SignedKeyRequest',
30
- type: 'tuple',
31
- },
32
- ] as const
33
-
34
- const hubResponseSchema = z.object({
35
- events: z.array(
36
- z.object({
37
- signerEventBody: z.object({
38
- key: z.string(),
39
- metadata: z.string(),
40
- }),
41
- }),
42
- ),
43
- })
44
-
45
- export const createVerifyAppKeyWithHub: (
46
- hubUrl: string,
47
- requestOptions?: RequestInit,
48
- ) => VerifyAppKey =
49
- (hubUrl, requestOptions) =>
50
- async (fid: number, appKey: string): Promise<VerifyAppKeyResult> => {
51
- const url = new URL('/v1/onChainSignersByFid', hubUrl)
52
- url.searchParams.append('fid', fid.toString())
53
-
54
- const response = await fetch(url, requestOptions)
55
-
56
- if (response.status !== 200) {
57
- throw new BaseError(`Non-200 response received: ${await response.text()}`)
58
- }
59
-
60
- const responseJson = await response.json()
61
- const parsedResponse = hubResponseSchema.safeParse(responseJson)
62
- if (parsedResponse.error) {
63
- throw new BaseError('Error parsing Hub response', parsedResponse.error)
64
- }
65
-
66
- const appKeyLower = appKey.toLowerCase()
67
-
68
- const signerEvent = parsedResponse.data.events.find(
69
- (event) => event.signerEventBody.key.toLowerCase() === appKeyLower,
70
- )
71
- if (!signerEvent) {
72
- return { valid: false }
73
- }
74
-
75
- const decoded = AbiParameters.decode(
76
- signedKeyRequestAbi,
77
- Buffer.from(signerEvent.signerEventBody.metadata, 'base64'),
78
- )
79
- if (decoded.length !== 1) {
80
- throw new BaseError('Error decoding metadata')
81
- }
82
-
83
- const appFid = Number(decoded[0].requestFid)
84
-
85
- return { valid: true, appFid }
86
- }
package/src/jfs.ts DELETED
@@ -1,151 +0,0 @@
1
- import {
2
- type EncodedJsonFarcasterSignatureSchema,
3
- encodedJsonFarcasterSignatureSchema,
4
- jsonFarcasterSignatureHeaderSchema,
5
- } from '@farcaster/frame-core'
6
- import { ed25519 } from '@noble/curves/ed25519'
7
- import {
8
- BaseError,
9
- type VerifyAppKey,
10
- type VerifyAppKeyResult,
11
- type VerifyJfsResult,
12
- } from './types.ts'
13
- import { bytesToHex, hexToBytes } from './util.ts'
14
-
15
- export declare namespace VerifyJsonFarcasterSignature {
16
- type ErrorType =
17
- | InvalidJfsDataError
18
- | InvalidJfsAppKeyError
19
- | VerifyAppKeyError
20
- }
21
-
22
- export class InvalidJfsDataError<
23
- C extends Error | undefined = undefined,
24
- > extends BaseError<C> {
25
- override readonly name = 'VerifyJsonFarcasterSignature.InvalidDataError'
26
- }
27
-
28
- export class InvalidJfsAppKeyError<
29
- C extends Error | undefined = undefined,
30
- > extends BaseError<C> {
31
- override readonly name = 'VerifyJsonFarcasterSignature.InvalidAppKeyError'
32
- }
33
-
34
- export class VerifyAppKeyError<
35
- C extends Error | undefined = undefined,
36
- > extends BaseError<C> {
37
- override readonly name = 'VerifyJsonFarcasterSignature.VerifyAppKeyError'
38
- }
39
-
40
- export async function verifyJsonFarcasterSignature(
41
- data: unknown,
42
- verifyAppKey: VerifyAppKey,
43
- ): Promise<VerifyJfsResult> {
44
- //
45
- // Parse, decode and validate data
46
- //
47
-
48
- const body = encodedJsonFarcasterSignatureSchema.safeParse(data)
49
- if (body.success === false) {
50
- throw new InvalidJfsDataError('Error parsing data', body.error)
51
- }
52
-
53
- let headerData: any
54
- try {
55
- headerData = JSON.parse(
56
- Buffer.from(body.data.header, 'base64url').toString('utf-8'),
57
- )
58
- } catch (error: unknown) {
59
- throw new InvalidJfsDataError('Error decoding and parsing header')
60
- }
61
-
62
- const header = jsonFarcasterSignatureHeaderSchema.safeParse(headerData)
63
- if (header.success === false) {
64
- throw new InvalidJfsDataError('Error parsing header', header.error)
65
- }
66
-
67
- const payload = Buffer.from(body.data.payload, 'base64url')
68
-
69
- const signature = Buffer.from(body.data.signature, 'base64url')
70
- if (signature.byteLength !== 64) {
71
- throw new InvalidJfsDataError('Invalid signature length')
72
- }
73
-
74
- //
75
- // Verify the signature
76
- //
77
-
78
- const fid = header.data.fid
79
- const appKey = header.data.key
80
- const appKeyBytes = hexToBytes(appKey)
81
-
82
- const signedInput = new Uint8Array(
83
- Buffer.from(body.data.header + '.' + body.data.payload),
84
- )
85
-
86
- let verifyResult: boolean
87
- try {
88
- verifyResult = ed25519.verify(signature, signedInput, appKeyBytes)
89
- } catch (e) {
90
- throw new InvalidJfsDataError(
91
- 'Error checking signature',
92
- e instanceof Error ? e : undefined,
93
- )
94
- }
95
-
96
- if (!verifyResult) {
97
- throw new InvalidJfsDataError('Invalid signature')
98
- }
99
-
100
- //
101
- // Verify that the app key belongs to the FID
102
- //
103
-
104
- let appKeyResult: VerifyAppKeyResult
105
- try {
106
- appKeyResult = await verifyAppKey(fid, appKey)
107
- } catch (error: unknown) {
108
- throw new VerifyAppKeyError(
109
- 'Error verifying app key',
110
- error instanceof Error ? error : undefined,
111
- )
112
- }
113
-
114
- if (!appKeyResult.valid) {
115
- throw new InvalidJfsAppKeyError('App key not valid for FID')
116
- }
117
-
118
- return { fid, appFid: appKeyResult.appFid, payload }
119
- }
120
-
121
- export function createJsonFarcasterSignature({
122
- fid,
123
- type,
124
- privateKey,
125
- payload,
126
- }: {
127
- fid: number
128
- type: 'app_key'
129
- privateKey: Uint8Array
130
- payload: Uint8Array
131
- }): EncodedJsonFarcasterSignatureSchema {
132
- const publicKey = ed25519.getPublicKey(privateKey)
133
-
134
- const header = { fid, type, key: bytesToHex(publicKey) }
135
- const encodedHeader = Buffer.from(JSON.stringify(header)).toString(
136
- 'base64url',
137
- )
138
- const encodedPayload = Buffer.from(payload).toString('base64url')
139
- const signatureInput = new Uint8Array(
140
- Buffer.from(encodedHeader + '.' + encodedPayload, 'utf-8'),
141
- )
142
-
143
- const signature = ed25519.sign(signatureInput, privateKey)
144
- const encodedSignature = Buffer.from(signature).toString('base64url')
145
-
146
- return {
147
- header: encodedHeader,
148
- payload: encodedPayload,
149
- signature: encodedSignature,
150
- }
151
- }
package/src/neynar.ts DELETED
@@ -1,23 +0,0 @@
1
- import { createVerifyAppKeyWithHub } from './farcaster.ts'
2
- import type { VerifyAppKey, VerifyAppKeyResult } from './types.ts'
3
-
4
- const apiKey = process.env.NEYNAR_API_KEY || ''
5
-
6
- export const verifyAppKeyWithNeynar: VerifyAppKey = async (
7
- fid: number,
8
- appKey: string,
9
- ): Promise<VerifyAppKeyResult> => {
10
- if (!apiKey) {
11
- throw new Error(
12
- 'Environment variable NEYNAR_API_KEY needs to be set to use Neynar for app key verification',
13
- )
14
- }
15
-
16
- const verifier = createVerifyAppKeyWithHub('https://hub-api.neynar.com', {
17
- headers: {
18
- 'x-api-key': apiKey,
19
- },
20
- })
21
-
22
- return verifier(fid, appKey)
23
- }
package/src/types.ts DELETED
@@ -1,32 +0,0 @@
1
- import type { FrameServerEvent } from '@farcaster/frame-core'
2
-
3
- export type VerifyAppKeyResult =
4
- | { valid: true; appFid: number }
5
- | { valid: false }
6
-
7
- export type VerifyAppKey = (
8
- fid: number,
9
- appKey: string,
10
- ) => Promise<VerifyAppKeyResult>
11
-
12
- export type VerifyJfsResult = {
13
- fid: number
14
- appFid: number
15
- payload: Uint8Array
16
- }
17
-
18
- export type ParseWebhookEventResult = {
19
- fid: number
20
- appFid: number
21
- event: FrameServerEvent
22
- }
23
-
24
- export class BaseError<C extends Error | undefined = undefined> extends Error {
25
- override name = 'BaseError'
26
- cause: C
27
-
28
- constructor(message: string, cause?: C) {
29
- super(message)
30
- this.cause = cause as any
31
- }
32
- }
package/src/util.ts DELETED
@@ -1,9 +0,0 @@
1
- export function bytesToHex(bytes: Uint8Array): string {
2
- return `0x${Buffer.from(bytes).toString('hex')}`
3
- }
4
-
5
- export function hexToBytes(hex: string): Uint8Array {
6
- return Uint8Array.from(
7
- Buffer.from(hex.startsWith('0x') ? hex.slice(2) : hex, 'hex'),
8
- )
9
- }
package/src/webhook.ts DELETED
@@ -1,50 +0,0 @@
1
- import { serverEventSchema } from '@farcaster/frame-core'
2
- import {
3
- type VerifyJsonFarcasterSignature,
4
- verifyJsonFarcasterSignature,
5
- } from './jfs.ts'
6
- import {
7
- BaseError,
8
- type ParseWebhookEventResult,
9
- type VerifyAppKey,
10
- } from './types.ts'
11
-
12
- export declare namespace ParseWebhookEvent {
13
- type ErrorType =
14
- | VerifyJsonFarcasterSignature.ErrorType
15
- | InvalidEventDataError
16
- }
17
-
18
- export class InvalidEventDataError<
19
- C extends Error | undefined = undefined,
20
- > extends BaseError<C> {
21
- override readonly name = 'VerifyJsonFarcasterSignature.InvalidEventDataError'
22
- }
23
-
24
- export async function parseWebhookEvent(
25
- rawData: unknown,
26
- verifyAppKey: VerifyAppKey,
27
- ): Promise<ParseWebhookEventResult> {
28
- const { fid, appFid, payload } = await verifyJsonFarcasterSignature(
29
- rawData,
30
- verifyAppKey,
31
- )
32
-
33
- // Pase and validate event payload
34
- let payloadJson: any
35
- try {
36
- payloadJson = JSON.parse(Buffer.from(payload).toString('utf-8'))
37
- } catch (error: unknown) {
38
- throw new InvalidEventDataError(
39
- 'Error decoding and parsing payload',
40
- error instanceof Error ? error : undefined,
41
- )
42
- }
43
-
44
- const event = serverEventSchema.safeParse(payloadJson)
45
- if (event.success === false) {
46
- throw new InvalidEventDataError('Invalid event payload', event.error)
47
- }
48
-
49
- return { fid, appFid, event: event.data }
50
- }