-
-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: cast actions (wip) * nit: tweaks * feat: add `origin` to context to construct urls easier * fix: typecheck * nit: lint * nit: deeplink update https://warpcast.com/horsefacts.eth/0x6b2240df * fix: bad origins Picked up a global `origin` value lol. * fix: more origin fixes, replaced icons * nit: rename add action button * docs: add docs on cast actions * feat: add util `.message` and `.error` methods to action response * nit: line break Co-authored-by: jxom <jakemoxey@gmail.com> * nit: delete line break Co-authored-by: jxom <jakemoxey@gmail.com> * refactor: remove `.message` and `.error` helpers * feat: remove `origin` from `context`, use action for URL In devtools, `ButtonAddAction` doesn't currently works as '&' symbol is being unintentionally escaped with 'amp;'. * nit: lint * nit: tweaks Co-authored-by: jxom <jakemoxey@gmail.com> * refactor: `.action` to `.castAction` * nit: tweak * nit: rename button property * nit: tweak docs * chore: changeset --------- Co-authored-by: jxom <jakemoxey@gmail.com>
- Loading branch information
Showing
17 changed files
with
889 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"frog": patch | ||
--- | ||
|
||
Implemented "Cast Actions" support via `.castAction` handler. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { Frog } from 'frog' | ||
import { Button } from 'frog' | ||
|
||
export const app = new Frog() | ||
.frame('/', (c) => | ||
c.res({ | ||
image: ( | ||
<div | ||
tw="flex" | ||
style={{ | ||
alignItems: 'center', | ||
background: 'linear-gradient(to right, #432889, #17101F)', | ||
backgroundSize: '100% 100%', | ||
flexDirection: 'column', | ||
flexWrap: 'nowrap', | ||
height: '100%', | ||
justifyContent: 'center', | ||
textAlign: 'center', | ||
width: '100%', | ||
}} | ||
> | ||
<div | ||
style={{ | ||
color: 'white', | ||
fontSize: 60, | ||
fontStyle: 'normal', | ||
letterSpacing: '-0.025em', | ||
lineHeight: 1.4, | ||
marginTop: 30, | ||
padding: '0 120px', | ||
whiteSpace: 'pre-wrap', | ||
}} | ||
> | ||
Add Cast Action | ||
</div> | ||
</div> | ||
), | ||
intents: [ | ||
<Button.AddCastAction action="/action" name="Log This!" icon="log"> | ||
Add | ||
</Button.AddCastAction>, | ||
], | ||
}), | ||
) | ||
.castAction('/action', async (c) => { | ||
console.log( | ||
`Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ | ||
c.actionData.fid | ||
}`, | ||
) | ||
return c.res({ message: 'Action Succeeded' }) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
# Cast Actions | ||
|
||
Cast Actions let developers create custom buttons which users can install into their action bar on any Farcaster application (see the [spec](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa)). | ||
|
||
## Overview | ||
|
||
At a glance: | ||
|
||
1. User installs Cast Action via specific deeplink or by clicking on `<Button.AddCastAction>{:jsx}` element with a specified target `.castAction` route in a Frame. | ||
2. When the user presses the Cast Action button in the App, the App will make a `POST` request to the `.castAction` route. | ||
3. Frame performs any action and returns a response to the App. | ||
|
||
## Walkthrough | ||
|
||
Here is a trivial example on how to expose an action with 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({ | ||
image: ( | ||
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}> | ||
Add "Log this!" Action | ||
</div> | ||
), | ||
intents: [ | ||
<Button.AddCastAction | ||
action="/log-this" | ||
name="Log This!" | ||
icon="log" | ||
> | ||
Add | ||
</Button.AddCastAction>, | ||
] | ||
}) | ||
}) | ||
|
||
app.castAction('/log-this', (c) => { | ||
console.log( | ||
`Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ | ||
c.actionData.fid | ||
}`, | ||
) | ||
return c.res({ message:'Action Succeeded' }) | ||
}) | ||
``` | ||
|
||
::: | ||
|
||
::::steps | ||
|
||
### 1. Render Frame & Add Action Intent | ||
|
||
In the example above, we are rendering Add Action intent: | ||
|
||
1. `action` property is used to set the path to the cast action route. | ||
2. `name` property is used to set the name of the action. It must be less than 30 characters | ||
3. `icon` property is used to associate your Cast Action with one of the Octicons. You can see the supported list [here](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa). | ||
|
||
```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: ( | ||
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}> | ||
Add "Log this!" Action | ||
</div> | ||
), | ||
intents: [ | ||
<Button.AddCastAction | ||
action="/log-this" | ||
name="Log This!" | ||
icon="log" | ||
> | ||
Add | ||
</Button.AddCastAction>, | ||
] | ||
}) | ||
}) | ||
|
||
// ... | ||
``` | ||
|
||
|
||
### 2. Handle `/log-this` Requests | ||
|
||
Without a route handler to handle the Action request, the Cast Action will be meaningless. | ||
|
||
Thus, let's define a `/log-this` route to handle the the Cast Action: | ||
|
||
```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: ( | ||
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}> | ||
Add "Log this!" Action | ||
</div> | ||
), | ||
intents: [ | ||
<Button.AddCastAction | ||
action="/log-this" | ||
name="Log This!" | ||
icon="log" | ||
> | ||
Add | ||
</Button.AddCastAction>, | ||
] | ||
}) | ||
}) | ||
|
||
app.castAction('/log-this', (c) => { // [!code focus] | ||
console.log( // [!code focus] | ||
`Cast Action to ${JSON.stringify(c.actionData.castId)} from ${ // [!code focus] | ||
c.actionData.fid // [!code focus] | ||
}`, // [!code focus] | ||
) // [!code focus] | ||
return c.res({ message: 'Action Succeeded' }) // [!code focus] | ||
}) // [!code focus] | ||
``` | ||
|
||
A breakdown of the `/log-this` route handler: | ||
|
||
- `c.actionData` is never nullable and is always defined since Cast Actions always do `POST` request. | ||
- We are responding with a `c.res` response and specifying a `message` that will appear in the success toast. | ||
|
||
|
||
::: | ||
|
||
### 5. Bonus: Learn the API | ||
|
||
You can learn more about the transaction APIs here: | ||
|
||
- [`Frog.castAction` Reference](/reference/frog-cast-action) | ||
- [`Frog.castAction` Context Reference](/reference/frog-cast-action-context) | ||
- [`Frog.castAction` Response Reference](/reference/frog-cast-action-response) | ||
|
||
:::: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# Frog.castAction Context | ||
|
||
The `c` object is the parameter of the route handlers. It contains context about the current cast action. | ||
|
||
```tsx twoslash | ||
// @noErrors | ||
import { Frog } from 'frog' | ||
|
||
export const app = new Frog() | ||
|
||
app.castAction('/', (c) => { // [!code focus] | ||
return c.res({/* ... */}) | ||
}) | ||
``` | ||
|
||
:::tip[Tip] | ||
An action handler can also be asynchronous (ie. `async (c) => { ... }{:js}`). | ||
::: | ||
|
||
## actionData | ||
|
||
- **Type**: `CastActionData` | ||
|
||
Data from the action that was passed via the `POST` body from a Farcaster Client. [See more.](https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa) | ||
|
||
```tsx twoslash | ||
// @noErrors | ||
/** @jsxImportSource frog/jsx */ | ||
// ---cut--- | ||
import { Button, Frog } from 'frog' | ||
|
||
export const app = new Frog() | ||
|
||
app.castAction('/', (c) => { | ||
const { actionData } = c | ||
const { castId, fid, messageHash, network, timestamp, url } = actionData // [!code focus] | ||
return c.res({/* ... */}) | ||
}) | ||
``` | ||
|
||
## 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.castAction('/', (c) => { | ||
const { req } = c // [!code focus] | ||
return c.res({/* ... */}) | ||
}) | ||
``` | ||
|
||
## res | ||
|
||
- **Type**: `(response: CastActionResponse) => CastActionResponse` | ||
|
||
The action response. | ||
|
||
```tsx twoslash | ||
// @noErrors | ||
/** @jsxImportSource frog/jsx */ | ||
// ---cut--- | ||
import { Button, Frog } from 'frog' | ||
|
||
export const app = new Frog() | ||
|
||
app.castAction('/', (c) => { | ||
return c.res({/* ... */}) // [!code focus] | ||
}) | ||
``` | ||
|
||
## var | ||
|
||
- **Type**: `HonoContext['var']` | ||
|
||
Extract a context value that was previously set via [`set`](#set) in [Middleware](/concepts/middleware). | ||
|
||
```tsx twoslash | ||
// @noErrors | ||
/** @jsxImportSource frog/jsx */ | ||
// ---cut--- | ||
import { Button, Frog } from 'frog' | ||
|
||
export const app = new Frog() | ||
|
||
app.use(async (c, next) => { | ||
c.set('message', 'Frog is cool!!') | ||
await next() | ||
}) | ||
|
||
app.castAction('/', (c) => { | ||
const message = c.var.message // [!code focus] | ||
return c.res({/* ... */}) | ||
}) | ||
``` | ||
|
||
## verified | ||
|
||
- **Type**: `boolean` | ||
|
||
Whether or not the [`actionData`](#actiondata) (and [`buttonIndex`](#buttonindex)) 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.castAction('/', (c) => { | ||
const { verified } = c // [!code focus] | ||
return c.res({/* ... */}) | ||
}) | ||
``` |
Oops, something went wrong.