diff --git a/bun.lockb b/bun.lockb
index d26d007e..d1623063 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/example/src/index.tsx b/example/src/index.tsx
index feb34b36..1245e903 100644
--- a/example/src/index.tsx
+++ b/example/src/index.tsx
@@ -2,43 +2,44 @@
/** @jsxImportSource hono/jsx */
/** @jsxFrag */
-import { Button, Framework } from "@wevm/framework";
+import { Button, Framework } from '@wevm/framework'
-const app = new Framework();
+const app = new Framework()
-app.frame("/", ({ untrustedData }) => {
- const { buttonIndex } = untrustedData || {};
+app.frame('/', ({ untrustedData }) => {
+ const { buttonIndex } = untrustedData || {}
+ console.log('buttonIndex', buttonIndex)
return {
image: (
- {typeof buttonIndex === "number"
+ {typeof buttonIndex === 'number'
? `Button Index: ${buttonIndex}`
- : "Welcome!"}
+ : 'Welcome!'}
),
@@ -46,12 +47,15 @@ app.frame("/", ({ untrustedData }) => {
<>
+
>
),
- };
-});
+ }
+})
export default {
port: 3001,
fetch: app.fetch,
-};
+}
diff --git a/src/index.tsx b/src/index.tsx
index d9e12389..6d99e34d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,6 +3,14 @@ import { ImageResponse } from 'hono-og'
import { type JSXNode } from 'hono/jsx'
import { Window } from 'happy-dom'
import { jsxRenderer } from 'hono/jsx-renderer'
+import * as ed from '@noble/ed25519'
+import { bytesToHex } from 'viem/utils'
+import {
+ NobleEd25519Signer,
+ makeFrameAction,
+ Message,
+ FrameActionBody,
+} from '@farcaster/core'
import {
type Frame,
@@ -30,34 +38,148 @@ type FrameReturnType = {
intents: JSX.Element
}
+const renderer = jsxRenderer(
+ ({ children }) => {
+ return (
+
+ {children}
+
+ )
+ },
+ { docType: true },
+)
+
export class Framework extends Hono {
frame(
path: string,
handler: (c: FrameContext) => FrameReturnType | Promise,
) {
- this.get(
- '/preview',
- jsxRenderer(
- ({ children }) => {
- return (
-
- {children}
-
- )
- },
- { docType: true },
- ),
- )
+ this.get('/preview', renderer)
+ this.post('/preview', renderer)
this.get('/preview/*', async (c) => {
const baseUrl = c.req.url.replace('/preview', '')
const response = await fetch(baseUrl)
- const html = await response.text()
- const frame = htmlToFrame(html)
+ const text = await response.text()
+ const frame = htmlToFrame(text)
return c.render(
-
-
-
,
+ <>
+
+ >,
+ )
+ })
+
+ this.post('/preview', async (c) => {
+ const baseUrl = c.req.url.replace('/preview', '')
+
+ const formData = await c.req.formData()
+ const buttonIndex = parseInt(
+ typeof formData.get('buttonIndex') === 'string'
+ ? (formData.get('buttonIndex') as string)
+ : '',
+ )
+ const inputText = formData.get('inputText')
+ ? Buffer.from(formData.get('inputText') as string)
+ : undefined
+
+ const privateKeyBytes = ed.utils.randomPrivateKey()
+ // const publicKeyBytes = await ed.getPublicKeyAsync(privateKeyBytes)
+
+ // const key = bytesToHex(publicKeyBytes)
+ // const deadline = Math.floor(Date.now() / 1000) + 60 * 60 // now + hour
+ //
+ // const account = privateKeyToAccount(bytesToHex(privateKeyBytes))
+ // const requestFid = 1
+
+ // const signature = await account.signTypedData({
+ // domain: {
+ // name: 'Farcaster SignedKeyRequestValidator',
+ // version: '1',
+ // chainId: 10,
+ // verifyingContract: '0x00000000FC700472606ED4fA22623Acf62c60553',
+ // },
+ // types: {
+ // SignedKeyRequest: [
+ // { name: 'requestFid', type: 'uint256' },
+ // { name: 'key', type: 'bytes' },
+ // { name: 'deadline', type: 'uint256' },
+ // ],
+ // },
+ // primaryType: 'SignedKeyRequest',
+ // message: {
+ // requestFid: BigInt(requestFid),
+ // key,
+ // deadline: BigInt(deadline),
+ // },
+ // })
+
+ // const response = await fetch(
+ // 'https://api.warpcast.com/v2/signed-key-requests',
+ // {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // deadline,
+ // key,
+ // requestFid,
+ // signature,
+ // }),
+ // },
+ // )
+
+ const fid = 2
+ const castId = {
+ fid,
+ hash: new Uint8Array(
+ Buffer.from('0000000000000000000000000000000000000000', 'hex'),
+ ),
+ }
+ const frameActionBody = FrameActionBody.create({
+ url: Buffer.from(baseUrl),
+ buttonIndex,
+ castId,
+ inputText,
+ })
+ const frameActionMessage = await makeFrameAction(
+ frameActionBody,
+ { fid, network: 1 },
+ new NobleEd25519Signer(privateKeyBytes),
+ )
+
+ const message = frameActionMessage._unsafeUnwrap()
+ const response = await fetch(baseUrl, {
+ method: 'POST',
+ body: JSON.stringify({
+ untrustedData: {
+ buttonIndex,
+ castId: {
+ fid: castId.fid,
+ hash: bytesToHex(castId.hash),
+ },
+ fid,
+ inputText,
+ messageHash: bytesToHex(message.hash),
+ network: 1,
+ timestamp: message.data.timestamp,
+ url: baseUrl,
+ },
+ trustedData: {
+ messageBytes: Buffer.from(
+ Message.encode(message).finish(),
+ ).toString('hex'),
+ },
+ }),
+ })
+ const text = await response.text()
+ // TODO: handle redirects
+ const frame = htmlToFrame(text)
+
+ return c.render(
+ <>
+
+ >,
)
})
@@ -103,6 +225,40 @@ export class Framework extends Hono {
////////////////////////////////////////////////////////////////////////
// Components
+type FramePreviewProps = {
+ baseUrl: string
+ frame: Frame
+}
+
+function FramePreview({ baseUrl, frame }: FramePreviewProps) {
+ return (
+
+ )
+}
+
export type ButtonProps = {
children: string
}
diff --git a/src/package.json b/src/package.json
index c77d0c0e..57c140cc 100644
--- a/src/package.json
+++ b/src/package.json
@@ -13,7 +13,10 @@
"hono": "^3"
},
"dependencies": {
+ "@farcaster/core": "^0.13.7",
+ "@noble/ed25519": "^2.0.0",
"happy-dom": "^13.3.8",
- "hono-og": "~0.0.2"
+ "hono-og": "~0.0.2",
+ "viem": "^2.7.6"
}
}