From 7d0a12e2df4692107ed91748736c6fc6be3c83c2 Mon Sep 17 00:00:00 2001 From: "moxey.eth" Date: Tue, 5 Mar 2024 13:58:28 +1100 Subject: [PATCH] docs: tx docs --- site/pages/concepts/transactions.mdx | 370 ++++++++++++++++ .../reference/frog-transaction-context.mdx | 395 ++++++++++++++++++ .../reference/frog-transaction-response.mdx | 363 ++++++++++++++++ site/pages/reference/frog-transaction.mdx | 103 +++++ site/vocs.config.tsx | 28 +- src/types/context.ts | 5 + src/types/frame.ts | 1 + src/utils/getFrameContext.ts | 1 + 8 files changed, 1262 insertions(+), 4 deletions(-) create mode 100644 site/pages/concepts/transactions.mdx create mode 100644 site/pages/reference/frog-transaction-context.mdx create mode 100644 site/pages/reference/frog-transaction-response.mdx create mode 100644 site/pages/reference/frog-transaction.mdx diff --git a/site/pages/concepts/transactions.mdx b/site/pages/concepts/transactions.mdx new file mode 100644 index 00000000..99e85dee --- /dev/null +++ b/site/pages/concepts/transactions.mdx @@ -0,0 +1,370 @@ +# Transactions + +Farcaster Frames have the ability to instruct an App to invoke and perform Ethereum transactions (see the [spec](https://warpcast.notion.site/Frame-Transactions-Public-Draft-v2-9d9f9f4f527249519a41bd8d16165f73?pvs=4)). + +## Overview + +At a glance: + +1. A Frame has a `{:jsx}` element with a specified target `.transaction` route. +2. When the user presses the button in the App, the App will make a `POST` request to the `.transaction` route. +3. The App uses the response to forward the transaction data to the user's wallet for signing and broadcasting. +4. Once the user has sent the transaction, the App will perform a `POST` request to the frame. + +## Walkthrough + +Here is a trivial example on how to expose a transaction interface in a frame. We will break it down below. + +:::code-group + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +// ---cut--- +import { Button, Frog, TextInput, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + action: '/finish', + image: ( +
+ Perform a transaction +
+ ), + intents: [ + , + Send Ether, + Mint, + ] + }) +}) + +app.frame('/finish', (c) => { + const { transactionId } = c + return c.res({ + image: ( +
+ Transaction ID: {transactionId} +
+ ) + }) +}) + +app.transaction('/send-ether', (c) => { + const { inputText } = c + // Send transaction response. + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther(inputText), + }) +}) + +app.transaction('/mint', (c) => { + const { inputText } = c + // Contract transaction response. + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', + args: [69420n], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther(inputText) + }) +}) +``` + +```tsx twoslash [src/abi.ts] filename="./abi.ts" +export const abi = [ + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const +``` + +::: + +::::steps + +### 1. Render Frame & Intents + +In the example above, we are rendering three transaction intents: + +1. A **text input** to capture the amount of ether to send with the transaction. +2. A **"Send Ether" button** that will call the `/send-ether` route, and expose a "send ethereum to an address" interface to the App. +3. A **"Mint" button** that will call the `/mint` route, and expose a "mint NFT" interface to the App. + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Perform a transaction +
+ ), + intents: [ + , + Send Ether, + Mint, + ] + }) +}) + +// ... +``` + + +### 2. Handle `/send-ether` Requests + +Without route handlers to handle these requests, these buttons will be meaningless. + +Firstly, let's define a `/send-ether` route to handle the "Send Ether" button: + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Perform a transaction +
+ ), + intents: [ + , + Send Ether, // [!code focus] + Mint, + ] + }) +}) + +// ... + +app.transaction('/send-ether', (c) => { // [!code focus] + const { inputText } = c // [!code focus] + // Send transaction response. // [!code focus] + return c.send({ // [!code focus] + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther(inputText), // [!code focus] + }) // [!code focus] +}) // [!code focus] +``` + +A breakdown of the `/send-ether` route handler: + +- We are responding with a `c.send` ("send transaction") response. +- We are extracting user input from the previous frame via `inputText`. +- Within `c.send`, we can specify a: + - `chainId`: CAIP-2 compliant chain ID. We are sending to `eip155:1` where `1` is Ethereum mainnet. + - `to`: a recipient. + - `value`: the amount of wei to send. We are using `parseEther` to convert ether → wei. + - `data`: optional calldata to include in the transaction. + - `abi`: optional ABI to include in the transaction. +- The `c.send` function constructs a [well-formed JSON response](https://warpcast.notion.site/Frame-Transactions-Public-Draft-v2-9d9f9f4f527249519a41bd8d16165f73?pvs=4) to be consumed by the App. + +:::tip[Tip] +We can also utilize the context to extract things like [frame data](/reference/frog-transaction-context#framedata), +[button index/value](/reference/frog-transaction-context#buttonvalue) or [input value](/reference/frog-transaction-context#inputvalue) that was interacted with, and [more](/reference/frog-transaction-context): + +```tsx twoslash +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.transaction('/send-ether', (c) => { + const { buttonValue, inputText, frameData } = c // [!code focus] + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther(inputText), + }) +}) +``` +::: + +### 3. Handle `/mint` Requests + +Secondly, let's define a `/mint` route to handle the "Mint" button: + +:::code-group + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' + +export const app = new Frog() +// ---cut--- +import { abi } from './abi' + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Perform a transaction +
+ ), + intents: [ + , + Send Ether, + Mint, // [!code focus] + ] + }) +}) + +// ... + +app.transaction('/mint', (c) => { // [!code focus] + const { inputText } = c // [!code focus] + // Contract transaction response. // [!code focus] + return c.contract({ // [!code focus] + abi, // [!code focus] + functionName: 'mint', // [!code focus] + args: [69420n], // [!code focus] + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther(inputText) // [!code focus] + }) // [!code focus] +}) // [!code focus] +``` + +```tsx twoslash [src/abi.ts] filename="./abi.ts" +export const abi = [ + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const +``` + +::: + +A breakdown of the `/mint` route handler: + +- We are responding with a `c.contract` ("contract transaction") response. +- We are extracting user input from the previous frame via `inputText`. +- Within `c.contract`, we can specify a: + - `abi`: ABI for the contract. + - `functionName`: Function to call on the contract. + - `args`: Arguments to supply to the function. + - `chainId`: CAIP-2 compliant chain ID. + - `to`: Contract address. + - `value`: Optional amount of wei to send to the payable function. +- The `c.contract` function constructs a [well-formed JSON response](https://warpcast.notion.site/Frame-Transactions-Public-Draft-v2-9d9f9f4f527249519a41bd8d16165f73?pvs=4) to be consumed by the App. + +### 4. Handle Post-Transaction Execution + +Once the user has sent the transaction, the App will perform a `POST` request to the frame. + +We can extract the transaction ID from context via `c.transactionId`. + +:::note +Note that if you don't specify an [`action` on the frame](/reference/frog-frame-response#action), the App will perform a request to the same frame. +::: + +:::code-group + +```tsx twoslash [src/index.tsx] +// @noErrors +/** @jsxImportSource hono/jsx */ +import { Button, Frog, parseEther } from 'frog' +import { abi } from './abi' + +export const app = new Frog() +// ---cut--- +app.frame('/', (c) => { + return c.res({ + action: '/finish', // [!code focus] + image: ( +
+ Perform a transaction +
+ ), + intents: [ + Send Ether, + Mint, + ] + }) +}) + +app.frame('/finish', (c) => { // [!code focus] + const { transactionId } = c // [!code focus] + return c.res({ // [!code focus] + image: ( // [!code focus] +
// [!code focus] + Transaction ID: {transactionId} // [!code focus] +
// [!code focus] + ) // [!code focus] + }) // [!code focus] +}) // [!code focus] + +app.transaction('/send-ether', (c) => { + // Send transaction response. + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('1'), + }) +}) + +app.transaction('/mint', (c) => { + // Contract transaction response. + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', + args: [69420n], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B' + }) +}) +``` + +```tsx twoslash [src/abi.ts] filename="./abi.ts" +export const abi = [ + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const +``` + +::: + +### 5. Bonus: Learn the API + +You can learn more about the transaction APIs here: + +- [`Frog.transaction` Reference](/reference/frog-transaction) +- [`Frog.transaction` Context Reference](/reference/frog-transaction-context) +- [`Frog.transaction` Response Reference](/reference/frog-transaction-response) + +:::: \ No newline at end of file diff --git a/site/pages/reference/frog-transaction-context.mdx b/site/pages/reference/frog-transaction-context.mdx new file mode 100644 index 00000000..8953be90 --- /dev/null +++ b/site/pages/reference/frog-transaction-context.mdx @@ -0,0 +1,395 @@ +# Frog.transaction Context + +The `c` object is the parameter of the route handlers. It contains context for the transaction route. + +```tsx twoslash +// @noErrors +import { Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { // [!code focus] + return c.send({/* ... */}) +}) +``` + +## buttonIndex + +- **Type**: `number` + +The index of the button that was previously clicked. + +For example, if the user clicked `"Banana"`, then `buttonIndex` will be `2`. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Select a fruit. +
+ ), + intents: [ + // [!code focus] + Apple // [!code focus] + , // [!code focus] + // [!code focus] + Banana // [!code focus] + , // [!code focus] + ] + }) +}) + +app.transaction('/send-ether', (c) => { + const { buttonIndex } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## buttonValue + +- **Type**: `string` + +The value of the button that was previously clicked. + +For example, if the user clicked `"Banana"`, then `buttonValue` will be `"banana"`. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Select a fruit. +
+ ), + intents: [ + // [!code focus] + Apple // [!code focus] + , // [!code focus] + // [!code focus] + Banana // [!code focus] + , // [!code focus] + ] + }) +}) + +app.transaction('/send-ether', (c) => { + const { buttonValue } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## contract + +- **Type**: `TransactionResponseFn` + +Contract transaction response. [See more.](/reference/frog-transaction-response#contract-transaction-ccontract) + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({/* ... */}) // [!code focus] +}) +``` + +## frameData + +- **Type**: `FrameData` + +Data from the frame that was passed via the `POST` body from a Farcaster Client. [See more.](https://docs.farcaster.xyz/reference/frames/spec#frame-signature-packet) + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + const { frameData } = c + const { castId, fid, messageHash, network, timestamp, url } = frameData // [!code focus] + return c.send({/* ... */}) +}) +``` + +## initialPath + +- **Type**: `string` + +Initial/start path of the frame set. + +If the user clicks `{:jsx}`, they will be directed to this URL. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + const { initialPath } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## inputText + +- **Type**: `string` + +The value of the input that was previously interacted with. + +For example, if the user typed `"Banana"`, then `inputText` will be `"Banana"`. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog, TextInput } from 'frog' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Send Ether +
+ ), + intents: [ + , // [!code focus] + , + ] + }) +}) + +app.transaction('/send-ether', (c) => { + const { inputText } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## previousButtonValues + +- **Type**: `string[]` + +The data of the previous intents. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.frame('/', (c) => { + return c.res({ + image: ( +
+ Select your fruit +
+ ), + intents: [ + // [!code focus] + Apple // [!code focus] + , // [!code focus] + // [!code focus] + Banana // [!code focus] + , // [!code focus] + ] + }) +}) + +app.transaction('/send-ether', (c) => { // [!code focus] + const { buttonValue, previousButtonValues } = c + console.log(previousButtonValues) // [!code focus] +// @log: ['apple', 'banana'] + return c.send({/* ... */}) +}) +``` + +## previousState + +- **Type**: `State` + +The state of the previous frame. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +type State = { + values: string[] +} + +export const app = new Frog({ + initialState: { + values: [] + } +}) + +app.frame('/', (c) => { + const { buttonValue, deriveState } = c + const state = deriveState(previousState => { + if (buttonValue) previousState.values.push(buttonValue) + }) + return c.res({ + image: ( +
+ {values.map(value =>
{value}
)} +
+ ), + intents: [ + , + , + Send, + ] + }) +}) + +app.transaction('/send-ether', (c) => { + const { previousState } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## req + +- **Type**: `Request` + +[Hono request object](https://hono.dev/api/request). + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + const { req } = c // [!code focus] + return c.res({/* ... */}) +}) +``` + +## res + +- **Type**: `TransactionResponseFn` + +Raw transaction response. [See more.](/reference/frog-transaction-response#raw-transaction-craw) + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.res({/* ... */}) // [!code focus] +}) +``` + +## send + +- **Type**: `TransactionResponseFn` + +Send transaction response. [See more.](/reference/frog-transaction-response#send-transaction-csend) + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({/* ... */}) // [!code focus] +}) +``` + +## verified + +- **Type**: `boolean` + +Whether or not the [`frameData`](#framedata) (and [`buttonIndex`](#buttonindex), [`buttonValue`](#buttonvalue), [`inputText`](#inputtext)) was verified by the Farcaster Hub API. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + const { verified } = c // [!code focus] + return c.send({/* ... */}) +}) +``` + +## url + +- **Type**: `string` + +Current URL. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + const { url } = c // [!code focus] + return c.send({/* ... */}) +}) +``` \ No newline at end of file diff --git a/site/pages/reference/frog-transaction-response.mdx b/site/pages/reference/frog-transaction-response.mdx new file mode 100644 index 00000000..078bb08b --- /dev/null +++ b/site/pages/reference/frog-transaction-response.mdx @@ -0,0 +1,363 @@ +# Frog.transaction Response + +The response returned from the `.transaction` handler. + +There are three types of responses: + +- [Send Transaction (`c.send`)](#send-transaction-csend): Convinience method to **send a transaction**. +- [Contract Transaction (`c.contract`)](#contract-transaction-ccontract): Convinience method to **invoke a contract function** (with inferred types & automatic encoding). +- [Raw Transaction (`c.raw`)](#raw-transaction-craw): Low-level method to **send raw transaction** (mimics the [Transaction Spec API](https://warpcast.notion.site/Frame-Transactions-Public-Draft-v2-9d9f9f4f527249519a41bd8d16165f73?pvs=4)). + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ // [!code focus] + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('1'), // [!code focus] + }) // [!code focus] +}) + +app.transaction('/mint', (c) => { + return c.contract({ // [!code focus] + abi, // [!code focus] + chainId: 'eip155:1', // [!code focus] + functionName: 'mint', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) // [!code focus] +}) + +app.transaction('/raw-send', (c) => { + return c.res({ // [!code focus] + chainId: 'eip155:1', // [!code focus] + method: 'eth_sendTransaction', // [!code focus] + params: { // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: 1n, // [!code focus] + }, // [!code focus] + }) // [!code focus] +}) +``` + +## Send Transaction (`c.send`) + +### chainId + +- **Type:** `"eip155:${number}"` + +A CAIP-2 Chain ID to identify the transaction network. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('1') + }) +}) +``` + +### to + +- **Type:** `Address` + +Transaction recipient. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('1') + }) +}) +``` + +### value + +- **Type:** `Address` + +Value (in wei) to send with the transaction. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('1') // [!code focus] + }) +}) +``` + +### abi (optional) + +- **Type:** `Abi` + +The ABI of the contract. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + abi, // [!code focus] + }) +}) +``` + +### data (optional) + +- **Type:** `Hex` + +Calldata to send with the transaction + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/send-ether', (c) => { + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + data: '0xdeadbeef', // [!code focus] + }) +}) +``` + +## Contract Transaction (`c.contract`) + +### abi + +- **Type:** `Abi` + +The ABI of the contract. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, // [!code focus] + chainId: 'eip155:1', + functionName: 'mint', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }) +}) +``` + +### chainId + +- **Type:** `"eip155:${number}"` + +A CAIP-2 Chain ID to identify the transaction network. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, + chainId: 'eip155:1', // [!code focus] + functionName: 'mint', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }) +}) +``` + +### functionName + +- **Type:** `string` + +Function to invoke on the contract. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }) +}) +``` + +### args + +- **Type:** `unknown` + +Args to pass to the contract function. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', + args: [69420n], // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + }) +}) +``` + +### to + +- **Type:** `Address` + +The address of the contract. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', + args: [69420n], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + }) +}) +``` + +### value (optional) + +- **Type:** `Address` + +Value to send with the transaction. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/mint', (c) => { + return c.contract({ + abi, + chainId: 'eip155:1', + functionName: 'mint', + args: [69420n], + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('1'), // [!code focus] + }) +}) +``` + +## Raw Transaction (`c.raw`) + +### chainId + +- **Type:** `"eip155:${number}"` + +A CAIP-2 Chain ID to identify the transaction network. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/raw-send', (c) => { + return c.res({ + chainId: 'eip155:1', // [!code focus] + method: 'eth_sendTransaction', + params: { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: 1n, + }, + }) +}) +``` + +### method + +- **Type:** `"eth_sendTransaction"` + +A method ID to identify the type of transaction request. + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/raw-send', (c) => { + return c.res({ + chainId: 'eip155:1', + method: 'eth_sendTransaction', // [!code focus] + params: { + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: 1n, + }, + }) +}) +``` + +### params + +- **Type:** `EthSendTransactionParameters` + +Transaction parameters + +```tsx twoslash +// @noErrors +import { Frog, parseEther } from 'frog' + +export const app = new Frog() + +app.transaction('/raw-send', (c) => { + return c.res({ + chainId: 'eip155:1', + method: 'eth_sendTransaction', + params: { // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: 1n, // [!code focus] + }, // [!code focus] + }) +}) +``` \ No newline at end of file diff --git a/site/pages/reference/frog-transaction.mdx b/site/pages/reference/frog-transaction.mdx new file mode 100644 index 00000000..9d665d8f --- /dev/null +++ b/site/pages/reference/frog-transaction.mdx @@ -0,0 +1,103 @@ +# Frog.transaction + +## Import + +```tsx twoslash +import { Frog } from 'frog' +``` + +## Usage + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog, parseEther } from 'frog' + +const app = new Frog() + +app.transaction('/send-ether', (c) => { // [!code focus] + // Send transaction response. // [!code focus] + return c.send({ // [!code focus] + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('1'), // [!code focus] + }) // [!code focus] +}) // [!code focus] + +app.transaction('/mint', (c) => { // [!code focus] + // Contract transaction response. // [!code focus] + return c.contract({ // [!code focus] + abi, // [!code focus] + chainId: 'eip155:1', // [!code focus] + functionName: 'mint', // [!code focus] + args: [69420n], // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B' // [!code focus] + }) // [!code focus] +}) // [!code focus] +``` + +## Parameters + +### path + +- **Type:** `string` + +Path of the route. + +[Read more.](/concepts/routing) + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog, parseEther } from 'frog' + +const app = new Frog() + +app.transaction( + '/send-ether', // [!code focus] + (c) => { + // Send transaction response. + return c.send({ + chainId: 'eip155:1', + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', + value: parseEther('1'), + }) + } +) +``` + +### handler + +- **Type:** `(c: Context) => FrameResponse` + +Handler function for the route. + +```tsx twoslash +// @noErrors +/** @jsxImportSource frog/jsx */ +// ---cut--- +import { Button, Frog, parseEther } from 'frog' + +const app = new Frog() + +app.transaction( + '/send-ether', + (c) => { // [!code focus] + return c.send({ // [!code focus] + chainId: 'eip155:1', // [!code focus] + to: '0xd2135CfB216b74109775236E36d4b433F1DF507B', // [!code focus] + value: parseEther('1'), // [!code focus] + }) // [!code focus] + } // [!code focus] +) +``` + +#### Context Parameter + +[See the Context API.](/reference/frog-transaction-context) + +#### Transaction Response + +[See the Transaction Response API.](/reference/frog-transaction-response) diff --git a/site/vocs.config.tsx b/site/vocs.config.tsx index e3f85fe1..2742fb64 100644 --- a/site/vocs.config.tsx +++ b/site/vocs.config.tsx @@ -298,6 +298,10 @@ export default defineConfig({ text: 'Middleware', link: '/concepts/middleware', }, + { + text: 'Transactions', + link: '/concepts/transactions', + }, ], }, { @@ -349,11 +353,27 @@ export default defineConfig({ text: 'Frog Reference', items: [ { text: 'Frog', link: '/reference/frog' }, - { text: 'Frog.frame', link: '/reference/frog-frame' }, - { text: 'Frog.frame Context', link: '/reference/frog-frame-context' }, { - text: 'Frog.frame Response', - link: '/reference/frog-frame-response', + text: 'Frog.frame', + link: '/reference/frog-frame', + items: [ + { text: 'Context', link: '/reference/frog-frame-context' }, + { + text: 'Response', + link: '/reference/frog-frame-response', + }, + ], + }, + { + text: 'Frog.transaction', + link: '/reference/frog-transaction', + items: [ + { text: 'Context', link: '/reference/frog-transaction-context' }, + { + text: 'Response', + link: '/reference/frog-transaction-response', + }, + ], }, { text: 'Frog.hono', link: '/reference/frog-hono' }, ], diff --git a/src/types/context.ts b/src/types/context.ts index 8ed017e0..7d6cf0f3 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -76,6 +76,11 @@ export type FrameContext< req: Context_hono['req'] /** Frame response that includes frame properties such as: image, intents, action, etc */ res: FrameResponseFn + /** + * Transaction ID of the executed transaction (if any). Maps to: + * - Ethereum: a transaction hash + */ + transactionId?: FrameData['transactionId'] | undefined } export type TransactionContext< diff --git a/src/types/frame.ts b/src/types/frame.ts index 4507e7da..da309444 100644 --- a/src/types/frame.ts +++ b/src/types/frame.ts @@ -116,6 +116,7 @@ export type FrameData = { network: number state?: string | undefined timestamp: number + transactionId?: string | undefined url: string } diff --git a/src/utils/getFrameContext.ts b/src/utils/getFrameContext.ts index b5bc3d24..199580e5 100644 --- a/src/utils/getFrameContext.ts +++ b/src/utils/getFrameContext.ts @@ -63,6 +63,7 @@ export function getFrameContext( req, res: (data) => data, status, + transactionId: frameData?.transactionId, url, verified, }