From 908bed1fcff19c9d35cd9f1b9d1d10bcdee08b21 Mon Sep 17 00:00:00 2001 From: Vladyslav Dalechyn Date: Thu, 24 Oct 2024 22:21:51 +0300 Subject: [PATCH] feat: mini-app transactions, `frog/vercel` deprecation (#503) * feat: mini-app transaction requests and response listening * nit: lint * refactor: jsonrpc, split next and web * nit: lint * feat: `JsonRpcError` added * nit: lint * nit: better error * nit: error * chore: changesets * chore: up changeset * chore: changesets up * nit: remove `frog/next` * nit: stuff * nit: lint --- .changeset/red-books-tan.md | 13 ++++ services/frame/api/index.tsx | 2 +- site/pages/concepts/composer-actions.mdx | 6 +- site/pages/platforms/next.mdx | 4 +- site/pages/platforms/vercel.mdx | 6 +- src/{vercel => next}/handle.ts | 2 +- src/next/index.ts | 11 +-- src/next/postComposerActionMessage.ts | 37 ---------- src/package.json | 11 ++- src/vercel/index.ts | 1 - src/vercel/package.json | 5 -- src/web/actions/contractTransaction.ts | 68 +++++++++++++++++++ src/web/actions/createCast.ts | 20 ++++++ src/web/actions/internal/jsonRpc/errors.ts | 10 +++ .../listenForJsonRpcResponseMessage.ts | 27 ++++++++ .../jsonRpc/postJsonRpcRequestMessage.ts | 26 +++++++ src/web/actions/internal/jsonRpc/types.ts | 18 +++++ .../jsonRpc/waitForJsonRpcResponse.ts | 18 +++++ .../internal/postCreateCastRequestMessage.ts | 23 +++++++ .../postSendTransactionRequestMessage.ts | 20 ++++++ .../postSignTypedDataRequestMessage.ts | 27 ++++++++ .../internal/waitForCreateCastResponse.ts | 9 +++ .../waitForSendTransactionResponse.ts | 10 +++ .../internal/waitForSignTypedDataResponse.ts | 18 +++++ src/web/actions/sendTransaction.ts | 26 +++++++ src/web/actions/signTypedData.ts | 30 ++++++++ .../frames}/getFrameMetadata.test.ts | 0 src/{next => web/frames}/getFrameMetadata.ts | 2 +- .../frames}/isFrameRequest.test.ts | 0 src/{next => web/frames}/isFrameRequest.ts | 0 src/web/index.ts | 30 ++++++++ src/web/package.json | 5 ++ templates/next/app/page.tsx | 2 +- templates/vercel/api/index.tsx | 4 +- 34 files changed, 417 insertions(+), 74 deletions(-) create mode 100644 .changeset/red-books-tan.md rename src/{vercel => next}/handle.ts (89%) delete mode 100644 src/next/postComposerActionMessage.ts delete mode 100644 src/vercel/index.ts delete mode 100644 src/vercel/package.json create mode 100644 src/web/actions/contractTransaction.ts create mode 100644 src/web/actions/createCast.ts create mode 100644 src/web/actions/internal/jsonRpc/errors.ts create mode 100644 src/web/actions/internal/jsonRpc/listenForJsonRpcResponseMessage.ts create mode 100644 src/web/actions/internal/jsonRpc/postJsonRpcRequestMessage.ts create mode 100644 src/web/actions/internal/jsonRpc/types.ts create mode 100644 src/web/actions/internal/jsonRpc/waitForJsonRpcResponse.ts create mode 100644 src/web/actions/internal/postCreateCastRequestMessage.ts create mode 100644 src/web/actions/internal/postSendTransactionRequestMessage.ts create mode 100644 src/web/actions/internal/postSignTypedDataRequestMessage.ts create mode 100644 src/web/actions/internal/waitForCreateCastResponse.ts create mode 100644 src/web/actions/internal/waitForSendTransactionResponse.ts create mode 100644 src/web/actions/internal/waitForSignTypedDataResponse.ts create mode 100644 src/web/actions/sendTransaction.ts create mode 100644 src/web/actions/signTypedData.ts rename src/{next => web/frames}/getFrameMetadata.test.ts (100%) rename src/{next => web/frames}/getFrameMetadata.ts (89%) rename src/{next => web/frames}/isFrameRequest.test.ts (100%) rename src/{next => web/frames}/isFrameRequest.ts (100%) create mode 100644 src/web/index.ts create mode 100644 src/web/package.json diff --git a/.changeset/red-books-tan.md b/.changeset/red-books-tan.md new file mode 100644 index 00000000..de8b08a3 --- /dev/null +++ b/.changeset/red-books-tan.md @@ -0,0 +1,13 @@ +--- +"frog": minor +--- + +**Breaking Change**: `frog/vercel` was deleted. If you used `handle` from this package, import it from `frog/next`. +**Breaking Change:** `frog/next` no longer exports `postComposerCreateCastActionMessage`. Use `createCast` from `frog/web`. + +Introduced `frog/web` for client-side related logic in favor of `frog/next`. +For backwards compatibility, all the previous exports are kept, but will be +deprecated in future, except for NextJS related `handle` function. + +Added functionality for the Mini-App JSON-RPC requests. [See more](https://warpcast.notion.site/Miniapp-Transactions-1216a6c0c10180b7b9f4eec58ec51e55). +Added `createCast`, `sendTransaction`, `contractTransaction` and `signTypedData` to `frog/web`. diff --git a/services/frame/api/index.tsx b/services/frame/api/index.tsx index 64a84084..0e0b4213 100644 --- a/services/frame/api/index.tsx +++ b/services/frame/api/index.tsx @@ -1,7 +1,7 @@ import { Button, Frog } from 'frog' import { devtools } from 'frog/dev' +import { handle } from 'frog/next' import { serveStatic } from 'frog/serve-static' -import { handle } from 'frog/vercel' type State = { featureIndex: number diff --git a/site/pages/concepts/composer-actions.mdx b/site/pages/concepts/composer-actions.mdx index 1d907959..39ef4d5e 100644 --- a/site/pages/concepts/composer-actions.mdx +++ b/site/pages/concepts/composer-actions.mdx @@ -52,15 +52,15 @@ app.composerAction( ``` ### Client-Side Helpers -Frog exports `postComposerCreateCastActionMessage` helper to post the message to the `window.parent`. +Frog exports `createCast` helper to post the message to the `window.parent`. ```tsx twoslash [src/index.tsx] // @noErrors -import { postComposerCreateCastActionMessage } from 'frog/next' +import { createCast } from 'frog/web' function App() { return ( - ) diff --git a/site/pages/platforms/next.mdx b/site/pages/platforms/next.mdx index 775b1fcd..c97dc4f3 100644 --- a/site/pages/platforms/next.mdx +++ b/site/pages/platforms/next.mdx @@ -268,7 +268,7 @@ This leverages the [`generateMetadata`](https://nextjs.org/docs/app/api-referenc ```tsx twoslash [app/page.tsx] // @noErrors -import { getFrameMetadata } from 'frog/next' +import { getFrameMetadata } from 'frog/web' import type { Metadata } from 'next' export async function generateMetadata(): Promise { @@ -291,7 +291,7 @@ If you use suspended components in a page, route Next.js will stream the respons // @noErrors import { headers } from 'next/headers' import type { Metadata } from 'next' -import { getFrameMetadata, isFrameRequest } from 'frog/next' +import { getFrameMetadata, isFrameRequest } from 'frog/web' import { SuspendedComponent } from './suspense-component' diff --git a/site/pages/platforms/vercel.mdx b/site/pages/platforms/vercel.mdx index 7023cf1b..9da2af5f 100644 --- a/site/pages/platforms/vercel.mdx +++ b/site/pages/platforms/vercel.mdx @@ -91,7 +91,7 @@ After that, we will append Vercel handlers to the file. /** @jsxImportSource frog/jsx */ // ---cut--- import { Button, Frog } from 'frog' -import { handle } from 'frog/vercel' // [!code focus] +import { handle } from 'frog/next' // [!code focus] // Uncomment to use Edge Runtime. // export const config = { @@ -135,7 +135,7 @@ Add Frog [Devtools](/concepts/devtools) after all frames are defined. This way t /** @jsxImportSource frog/jsx */ // ---cut--- import { Button, Frog } from 'frog' -import { handle } from 'frog/vercel' +import { handle } from 'frog/next' import { devtools } from 'frog/dev' // [!code focus] import { serveStatic } from 'frog/serve-static' // [!code focus] @@ -210,7 +210,7 @@ they will be redirected to the `/` path. /** @jsxImportSource frog/jsx */ // ---cut--- import { Button, Frog } from 'frog' -import { handle } from 'frog/vercel' +import { handle } from 'frog/next' // Uncomment to use Edge Runtime. // export const config = { diff --git a/src/vercel/handle.ts b/src/next/handle.ts similarity index 89% rename from src/vercel/handle.ts rename to src/next/handle.ts index 8b05c063..46e6ec64 100644 --- a/src/vercel/handle.ts +++ b/src/next/handle.ts @@ -1,7 +1,7 @@ import type { Schema } from 'hono' import { handle as handle_hono } from 'hono/vercel' -import type { Frog } from '../frog.js' +import type { Frog } from '../frog.jsx' import type { Env } from '../types/env.js' export function handle< diff --git a/src/next/index.ts b/src/next/index.ts index a63f9607..8ebbc90e 100644 --- a/src/next/index.ts +++ b/src/next/index.ts @@ -1,10 +1 @@ -// TODO: Rename this package to `js` as most of it doesn't strictly depend -// on Next.JS specific features. Only `handle` does. - -export { getFrameMetadata } from './getFrameMetadata.js' -export { handle } from '../vercel/index.js' -export { isFrameRequest } from './isFrameRequest.js' -export { - postComposerActionMessage, - postComposerCreateCastActionMessage, -} from './postComposerActionMessage.js' +export { handle } from './handle.js' diff --git a/src/next/postComposerActionMessage.ts b/src/next/postComposerActionMessage.ts deleted file mode 100644 index e4d9f8c9..00000000 --- a/src/next/postComposerActionMessage.ts +++ /dev/null @@ -1,37 +0,0 @@ -export type ComposerActionMessage = { - type: 'createCast' - data: { - cast: { - embeds: string[] - text: string - } - } -} - -/** - * Posts Composer Action Message to `window.parent`. - */ -export function postComposerActionMessage(message: ComposerActionMessage) { - if (typeof window === 'undefined') - throw new Error( - '`postComposerActionMessage` must be called in the Client Component.', - ) - - window.parent.postMessage(message, '*') -} - -/** - * Posts Composer Create Cast Action Message to `window.parent`. - * - * This is a convinience method and it calls `postComposerActionMessage` under the hood. - */ -export function postComposerCreateCastActionMessage( - message: ComposerActionMessage['data']['cast'], -) { - return postComposerActionMessage({ - type: 'createCast', - data: { - cast: message, - }, - }) -} diff --git a/src/package.json b/src/package.json index e0c8daee..b1387c4d 100644 --- a/src/package.json +++ b/src/package.json @@ -84,9 +84,9 @@ "types": "./_lib/ui/icons/radix-icons/index.d.ts", "default": "./_lib/ui/icons/radix-icons/index.js" }, - "./vercel": { - "types": "./_lib/vercel/index.d.ts", - "default": "./_lib/vercel/index.js" + "./web": { + "types": "./_lib/web/index.d.ts", + "default": "./_lib/web/index.js" } }, "peerDependencies": { @@ -121,10 +121,7 @@ "license": "MIT", "homepage": "https://frog.fm", "repository": "wevm/frog", - "authors": [ - "awkweb.eth", - "jxom.eth" - ], + "authors": ["awkweb.eth", "jxom.eth"], "funding": [ { "type": "github", diff --git a/src/vercel/index.ts b/src/vercel/index.ts deleted file mode 100644 index 8ebbc90e..00000000 --- a/src/vercel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { handle } from './handle.js' diff --git a/src/vercel/package.json b/src/vercel/package.json deleted file mode 100644 index dae01fa6..00000000 --- a/src/vercel/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "type": "module", - "types": "../_lib/vercel/index.d.ts", - "module": "../_lib/vercel/index.js" -} diff --git a/src/web/actions/contractTransaction.ts b/src/web/actions/contractTransaction.ts new file mode 100644 index 00000000..d0dd6cf9 --- /dev/null +++ b/src/web/actions/contractTransaction.ts @@ -0,0 +1,68 @@ +import { + type Abi, + AbiFunctionNotFoundError, + type ContractFunctionArgs, + type ContractFunctionName, + type EncodeFunctionDataParameters, + type GetAbiItemParameters, + encodeFunctionData, + getAbiItem, +} from 'viem' +import type { ContractTransactionParameters } from '../../types/transaction.js' +import type { JsonRpcResponseError } from './internal/jsonRpc/types.js' +import { postSendTransactionRequestMessage } from './internal/postSendTransactionRequestMessage.js' +import { + type EthSendTransactionSuccessBody, + waitForSendTransactionResponse, +} from './internal/waitForSendTransactionResponse.js' + +type ContractTransactionReturnType = EthSendTransactionSuccessBody +type ContractTransactionErrorType = JsonRpcResponseError +export type { + ContractTransactionParameters, + ContractTransactionReturnType, + ContractTransactionErrorType, +} + +export async function contractTransaction< + const abi extends Abi | readonly unknown[], + functionName extends ContractFunctionName, + args extends ContractFunctionArgs< + abi, + 'nonpayable' | 'payable', + functionName + >, +>( + parameters: ContractTransactionParameters, + requestIdOverride?: string, +): Promise { + const { abi, chainId, functionName, gas, to, args, attribution, value } = + parameters + + const abiItem = getAbiItem({ + abi: abi, + name: functionName, + args, + } as GetAbiItemParameters) + if (!abiItem) throw new AbiFunctionNotFoundError(functionName) + + const abiErrorItems = (abi as Abi).filter((item) => item.type === 'error') + + const requestId = postSendTransactionRequestMessage( + { + abi: [abiItem, ...abiErrorItems], + attribution, + chainId, + data: encodeFunctionData({ + abi, + args, + functionName, + } as EncodeFunctionDataParameters), + gas, + to, + value, + }, + requestIdOverride, + ) + return waitForSendTransactionResponse(requestId) +} diff --git a/src/web/actions/createCast.ts b/src/web/actions/createCast.ts new file mode 100644 index 00000000..d8a33e92 --- /dev/null +++ b/src/web/actions/createCast.ts @@ -0,0 +1,20 @@ +import type { JsonRpcResponseError } from './internal/jsonRpc/types.js' +import { + type CreateCastRequestMessageParameters, + postCreateCastRequestMessage, +} from './internal/postCreateCastRequestMessage.js' +import type { FcCreateCastSuccessBody } from './internal/waitForCreateCastResponse.js' +import { waitForCreateCastResponse } from './internal/waitForCreateCastResponse.js' + +type CreateCastParameters = CreateCastRequestMessageParameters +type CreateCastReturnType = FcCreateCastSuccessBody +type CreateCastErrorType = JsonRpcResponseError +export type { CreateCastParameters, CreateCastReturnType, CreateCastErrorType } + +export async function createCast( + parameters: CreateCastParameters, + requestIdOverride?: string, +): Promise { + const requestId = postCreateCastRequestMessage(parameters, requestIdOverride) + return waitForCreateCastResponse(requestId) +} diff --git a/src/web/actions/internal/jsonRpc/errors.ts b/src/web/actions/internal/jsonRpc/errors.ts new file mode 100644 index 00000000..dafbd09d --- /dev/null +++ b/src/web/actions/internal/jsonRpc/errors.ts @@ -0,0 +1,10 @@ +export class JsonRpcError extends Error { + code: number + requestId: string + constructor(requestId: string, code: number, message: string) { + super(message) + this.name = 'JsonRpcError' + this.code = code + this.requestId = requestId + } +} diff --git a/src/web/actions/internal/jsonRpc/listenForJsonRpcResponseMessage.ts b/src/web/actions/internal/jsonRpc/listenForJsonRpcResponseMessage.ts new file mode 100644 index 00000000..79f10c24 --- /dev/null +++ b/src/web/actions/internal/jsonRpc/listenForJsonRpcResponseMessage.ts @@ -0,0 +1,27 @@ +import type { JsonRpcResponseFailure, JsonRpcResponseSuccess } from './types.js' + +export function listenForJsonRpcResponseMessage( + handler: ( + message: JsonRpcResponseSuccess | JsonRpcResponseFailure, + ) => unknown, + requestId: string, +) { + if (typeof window === 'undefined') + throw new Error( + '`listenForJsonRpcResponseMessage` must be called in the Client Component.', + ) + + const listener = ( + event: MessageEvent< + JsonRpcResponseSuccess | JsonRpcResponseFailure + >, + ) => { + if (event.data.id !== requestId) return + + handler(event.data) + } + + window.parent.addEventListener('message', listener) + + return () => window.parent.removeEventListener('message', listener) +} diff --git a/src/web/actions/internal/jsonRpc/postJsonRpcRequestMessage.ts b/src/web/actions/internal/jsonRpc/postJsonRpcRequestMessage.ts new file mode 100644 index 00000000..83aeefce --- /dev/null +++ b/src/web/actions/internal/jsonRpc/postJsonRpcRequestMessage.ts @@ -0,0 +1,26 @@ +import type { JsonRpcMethod } from './types.js' + +export type PostJsonRpcRequestMessageReturnType = string + +export function postJsonRpcRequestMessage( + method: JsonRpcMethod, + parameters: any, + requestIdOverride?: string, +): PostJsonRpcRequestMessageReturnType { + if (typeof window === 'undefined') + throw new Error( + '`postJsonRpcRequestMessage` must be called in the Client Component.', + ) + + const requestId = requestIdOverride ?? crypto.randomUUID() + window.parent.postMessage( + { + jsonrpc: '2.0', + id: requestId, + method, + params: parameters, + }, + '*', + ) + return requestId +} diff --git a/src/web/actions/internal/jsonRpc/types.ts b/src/web/actions/internal/jsonRpc/types.ts new file mode 100644 index 00000000..6d3cb76f --- /dev/null +++ b/src/web/actions/internal/jsonRpc/types.ts @@ -0,0 +1,18 @@ +export type JsonRpcResponseSuccess = { + jsonrpc: '2.0' + id: string | number | null + result: resultType +} + +export type JsonRpcResponseError = { + code: number + message: string +} + +export type JsonRpcResponseFailure = { + jsonrpc: '2.0' + id: string | number | null + error: JsonRpcResponseError +} + +export type JsonRpcMethod = 'fc_requestWalletAction' | 'fc_createCast' diff --git a/src/web/actions/internal/jsonRpc/waitForJsonRpcResponse.ts b/src/web/actions/internal/jsonRpc/waitForJsonRpcResponse.ts new file mode 100644 index 00000000..845088f0 --- /dev/null +++ b/src/web/actions/internal/jsonRpc/waitForJsonRpcResponse.ts @@ -0,0 +1,18 @@ +import { JsonRpcError } from './errors.js' +import { listenForJsonRpcResponseMessage } from './listenForJsonRpcResponseMessage.js' + +export function waitForJsonRpcResponse( + requestId: string, +): Promise { + return new Promise((resolve, reject) => { + listenForJsonRpcResponseMessage((message) => { + if ('result' in message) { + resolve(message.result) + return + } + reject( + new JsonRpcError(requestId, message.error.code, message.error.message), + ) + }, requestId) + }) +} diff --git a/src/web/actions/internal/postCreateCastRequestMessage.ts b/src/web/actions/internal/postCreateCastRequestMessage.ts new file mode 100644 index 00000000..aa308eef --- /dev/null +++ b/src/web/actions/internal/postCreateCastRequestMessage.ts @@ -0,0 +1,23 @@ +import { + type PostJsonRpcRequestMessageReturnType, + postJsonRpcRequestMessage, +} from './jsonRpc/postJsonRpcRequestMessage.js' + +export type CreateCastRequestMessageParameters = { + embeds: string[] + text: string +} + +export type CreateCastRequestMessageReturnType = + PostJsonRpcRequestMessageReturnType + +export function postCreateCastRequestMessage( + parameters: CreateCastRequestMessageParameters, + requestIdOverride?: string, +) { + return postJsonRpcRequestMessage( + 'fc_createCast', + parameters, + requestIdOverride, + ) +} diff --git a/src/web/actions/internal/postSendTransactionRequestMessage.ts b/src/web/actions/internal/postSendTransactionRequestMessage.ts new file mode 100644 index 00000000..b1402c2e --- /dev/null +++ b/src/web/actions/internal/postSendTransactionRequestMessage.ts @@ -0,0 +1,20 @@ +import type { SendTransactionParameters } from '../../../types/transaction.js' +import { + type PostJsonRpcRequestMessageReturnType, + postJsonRpcRequestMessage, +} from './jsonRpc/postJsonRpcRequestMessage.js' + +export type SendTransactionRequestMessageParameters = SendTransactionParameters +export type SendTransactionRequestMessageReturnType = + PostJsonRpcRequestMessageReturnType + +export function postSendTransactionRequestMessage( + parameters: SendTransactionRequestMessageParameters, + requestIdOverride?: string, +) { + return postJsonRpcRequestMessage( + 'fc_requestWalletAction', + parameters, + requestIdOverride, + ) +} diff --git a/src/web/actions/internal/postSignTypedDataRequestMessage.ts b/src/web/actions/internal/postSignTypedDataRequestMessage.ts new file mode 100644 index 00000000..ddb0cc20 --- /dev/null +++ b/src/web/actions/internal/postSignTypedDataRequestMessage.ts @@ -0,0 +1,27 @@ +import type { TypedData } from 'viem' +import type { SignTypedDataParameters } from '../../../types/signature.js' +import { + type PostJsonRpcRequestMessageReturnType, + postJsonRpcRequestMessage, +} from './jsonRpc/postJsonRpcRequestMessage.js' + +export type SignTypedDataRequestMessageParameters< + typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +> = SignTypedDataParameters +export type SignTypedDataRequestMessageReturnType = + PostJsonRpcRequestMessageReturnType + +export function postSignTypedDataRequestMessage< + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + parameters: SignTypedDataRequestMessageParameters, + requestIdOverride?: string, +) { + return postJsonRpcRequestMessage( + 'fc_requestWalletAction', + parameters, + requestIdOverride, + ) +} diff --git a/src/web/actions/internal/waitForCreateCastResponse.ts b/src/web/actions/internal/waitForCreateCastResponse.ts new file mode 100644 index 00000000..83137b08 --- /dev/null +++ b/src/web/actions/internal/waitForCreateCastResponse.ts @@ -0,0 +1,9 @@ +import { waitForJsonRpcResponse } from './jsonRpc/waitForJsonRpcResponse.js' + +export type FcCreateCastSuccessBody = { + success: boolean +} + +export function waitForCreateCastResponse(requestId: string) { + return waitForJsonRpcResponse(requestId) +} diff --git a/src/web/actions/internal/waitForSendTransactionResponse.ts b/src/web/actions/internal/waitForSendTransactionResponse.ts new file mode 100644 index 00000000..6836b31b --- /dev/null +++ b/src/web/actions/internal/waitForSendTransactionResponse.ts @@ -0,0 +1,10 @@ +import { waitForJsonRpcResponse } from './jsonRpc/waitForJsonRpcResponse.js' + +export type EthSendTransactionSuccessBody = { + address: string + transactionHash: string +} + +export function waitForSendTransactionResponse(requestId: string) { + return waitForJsonRpcResponse(requestId) +} diff --git a/src/web/actions/internal/waitForSignTypedDataResponse.ts b/src/web/actions/internal/waitForSignTypedDataResponse.ts new file mode 100644 index 00000000..ec1084bb --- /dev/null +++ b/src/web/actions/internal/waitForSignTypedDataResponse.ts @@ -0,0 +1,18 @@ +import type { + JsonRpcResponseFailure, + JsonRpcResponseSuccess, +} from './jsonRpc/types.js' +import { waitForJsonRpcResponse } from './jsonRpc/waitForJsonRpcResponse.js' + +export type EthSignTypedDataResponse = + | JsonRpcResponseSuccess + | JsonRpcResponseFailure + +export type EthSignTypedDataSuccessBody = { + address: string + signature: string +} + +export function waitForSignTypedDataResponse(requestId: string) { + return waitForJsonRpcResponse(requestId) +} diff --git a/src/web/actions/sendTransaction.ts b/src/web/actions/sendTransaction.ts new file mode 100644 index 00000000..1a5bbf8e --- /dev/null +++ b/src/web/actions/sendTransaction.ts @@ -0,0 +1,26 @@ +import type { SendTransactionParameters } from '../../types/transaction.js' +import type { JsonRpcResponseError } from './internal/jsonRpc/types.js' +import { postSendTransactionRequestMessage } from './internal/postSendTransactionRequestMessage.js' +import { + type EthSendTransactionSuccessBody, + waitForSendTransactionResponse, +} from './internal/waitForSendTransactionResponse.js' + +type SendTransactionReturnType = EthSendTransactionSuccessBody +type SendTransactionErrorType = JsonRpcResponseError +export type { + SendTransactionParameters, + SendTransactionReturnType, + SendTransactionErrorType, +} + +export async function sendTransaction( + parameters: SendTransactionParameters, + requestIdOverride?: string, +): Promise { + const requestId = postSendTransactionRequestMessage( + parameters, + requestIdOverride, + ) + return waitForSendTransactionResponse(requestId) +} diff --git a/src/web/actions/signTypedData.ts b/src/web/actions/signTypedData.ts new file mode 100644 index 00000000..7e4486e5 --- /dev/null +++ b/src/web/actions/signTypedData.ts @@ -0,0 +1,30 @@ +import type { TypedData } from 'viem' +import type { SignTypedDataParameters } from '../../types/signature.js' +import type { JsonRpcResponseError } from './internal/jsonRpc/types.js' +import { postSignTypedDataRequestMessage } from './internal/postSignTypedDataRequestMessage.js' +import { + type EthSignTypedDataSuccessBody, + waitForSignTypedDataResponse, +} from './internal/waitForSignTypedDataResponse.js' + +type SignTypedDataReturnType = EthSignTypedDataSuccessBody +type SignTypedDataErrorType = JsonRpcResponseError +export type { + SignTypedDataParameters, + SignTypedDataReturnType, + SignTypedDataErrorType, +} + +export async function signTypedData< + const typedData extends TypedData | Record, + primaryType extends keyof typedData | 'EIP712Domain', +>( + parameters: SignTypedDataParameters, + requestIdOverride?: string, +): Promise { + const requestId = postSignTypedDataRequestMessage( + parameters, + requestIdOverride, + ) + return waitForSignTypedDataResponse(requestId) +} diff --git a/src/next/getFrameMetadata.test.ts b/src/web/frames/getFrameMetadata.test.ts similarity index 100% rename from src/next/getFrameMetadata.test.ts rename to src/web/frames/getFrameMetadata.test.ts diff --git a/src/next/getFrameMetadata.ts b/src/web/frames/getFrameMetadata.ts similarity index 89% rename from src/next/getFrameMetadata.ts rename to src/web/frames/getFrameMetadata.ts index e3b41d60..f809a3f1 100644 --- a/src/next/getFrameMetadata.ts +++ b/src/web/frames/getFrameMetadata.ts @@ -1,4 +1,4 @@ -import { getFrameMetadata as getFrameMetadata_ } from '../utils/getFrameMetadata.js' +import { getFrameMetadata as getFrameMetadata_ } from '../../utils/getFrameMetadata.js' /** * Extracts frame metadata from a given URL, designed to be used with Next.js' `generateMetadata` export function. diff --git a/src/next/isFrameRequest.test.ts b/src/web/frames/isFrameRequest.test.ts similarity index 100% rename from src/next/isFrameRequest.test.ts rename to src/web/frames/isFrameRequest.test.ts diff --git a/src/next/isFrameRequest.ts b/src/web/frames/isFrameRequest.ts similarity index 100% rename from src/next/isFrameRequest.ts rename to src/web/frames/isFrameRequest.ts diff --git a/src/web/index.ts b/src/web/index.ts new file mode 100644 index 00000000..f7423d0e --- /dev/null +++ b/src/web/index.ts @@ -0,0 +1,30 @@ +export { getFrameMetadata } from './frames/getFrameMetadata.js' +export { isFrameRequest } from './frames/isFrameRequest.js' + +export { + type ContractTransactionParameters, + type ContractTransactionReturnType, + type ContractTransactionErrorType, + contractTransaction, +} from './actions/contractTransaction.js' + +export { + type CreateCastParameters, + type CreateCastReturnType, + type CreateCastErrorType, + createCast, +} from './actions/createCast.js' + +export { + type SendTransactionParameters, + type SendTransactionReturnType, + type SendTransactionErrorType, + sendTransaction, +} from './actions/sendTransaction.js' + +export { + type SignTypedDataParameters, + type SignTypedDataReturnType, + type SignTypedDataErrorType, + signTypedData, +} from './actions/signTypedData.js' diff --git a/src/web/package.json b/src/web/package.json new file mode 100644 index 00000000..d2a8d0cb --- /dev/null +++ b/src/web/package.json @@ -0,0 +1,5 @@ +{ + "type": "module", + "types": "../_lib/web/index.d.ts", + "module": "../_lib/web/index.js" +} diff --git a/templates/next/app/page.tsx b/templates/next/app/page.tsx index d82beed1..28bb2726 100644 --- a/templates/next/app/page.tsx +++ b/templates/next/app/page.tsx @@ -1,4 +1,4 @@ -import { getFrameMetadata } from 'frog/next' +import { getFrameMetadata } from 'frog/web' import type { Metadata } from 'next' import Image from 'next/image' diff --git a/templates/vercel/api/index.tsx b/templates/vercel/api/index.tsx index 7b1ffa37..35aebbcc 100644 --- a/templates/vercel/api/index.tsx +++ b/templates/vercel/api/index.tsx @@ -1,8 +1,8 @@ import { Button, Frog, TextInput } from 'frog' import { devtools } from 'frog/dev' -import { serveStatic } from 'frog/serve-static' // import { neynar } from 'frog/hubs' -import { handle } from 'frog/vercel' +import { handle } from 'frog/next' +import { serveStatic } from 'frog/serve-static' // Uncomment to use Edge Runtime. // export const config = {