Transactions
Farcaster Frames have the ability to instruct an App to invoke and perform Ethereum transactions (see the spec).
Overview
At a glance:
- A Frame has a
<Button.Transaction>element with a specified target.transactionroute. - When the user presses the button in the App, the App will make a
POSTrequest to the.transactionroute. - The App uses the response to forward the transaction data to the user's wallet for signing and broadcasting.
- Once the user has sent the transaction, the App will perform a
POSTrequest 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.
import { Button, Frog, TextInput, parseEther } from 'frog'
import { abi } from './abi'
export const app = new Frog({ title: 'Frog Frame' })
app.frame('/', (c) => {
return c.res({
action: '/finish',
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Perform a transaction
</div>
),
intents: [
<TextInput placeholder="Value (ETH)" />,
<Button.Transaction target="/send-ether">Send Ether</Button.Transaction>,
<Button.Transaction target="/mint">Mint</Button.Transaction>,
]
})
})
app.frame('/finish', (c) => {
const { transactionId } = c
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Transaction ID: {transactionId}
</div>
)
})
})
app.transaction('/send-ether', (c) => {
const { inputText } = c
// Send transaction response.
return c.send({
chainId: 'eip155:10',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther(inputText),
})
})
app.transaction('/mint', (c) => {
const { inputText } = c
// Contract transaction response.
return c.contract({
abi,
chainId: 'eip155:10',
functionName: 'mint',
args: [69420n],
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther(inputText)
})
})1. Render Frame & Intents
In the example above, we are rendering three transaction intents:
- A text input to capture the amount of ether to send with the transaction.
- A "Send Ether" button that will call the
/send-etherroute, and expose a "send ethereum to an address" interface to the App. - A "Mint" button that will call the
/mintroute, and expose a "mint NFT" interface to the App.
app.frame('/', (c) => {
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Perform a transaction
</div>
),
intents: [
<TextInput placeholder="Value (ETH)" />,
<Button.Transaction target="/send-ether">Send Ether</Button.Transaction>,
<Button.Transaction target="/mint">Mint</Button.Transaction>,
]
})
})
// ...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:
app.frame('/', (c) => {
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Perform a transaction
</div>
),
intents: [
<TextInput placeholder="Value (ETH)" />,
<Button.Transaction target="/send-ether">Send Ether</Button.Transaction>,
<Button.Transaction target="/mint">Mint</Button.Transaction>,
]
})
})
// ...
app.transaction('/send-ether', (c) => {
const { inputText } = c
// Send transaction response.
return c.send({
chainId: 'eip155:10',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther(inputText),
})
})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 toeip155:10where1is Ethereum mainnet.to: a recipient.value: the amount of wei to send. We are usingparseEtherto convert ether → wei.data: optional calldata to include in the transaction.abi: optional ABI to include in the transaction.
- The
c.sendfunction constructs a well-formed JSON response to be consumed by the App.
3. Handle /mint Requests
Secondly, let's define a /mint route to handle the "Mint" button:
import { abi } from './abi'
app.frame('/', (c) => {
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Perform a transaction
</div>
),
intents: [
<TextInput placeholder="Value (ETH)" />,
<Button.Transaction target="/send-ether">Send Ether</Button.Transaction>,
<Button.Transaction target="/mint">Mint</Button.Transaction>,
]
})
})
// ...
app.transaction('/mint', (c) => {
const { inputText } = c
// Contract transaction response.
return c.contract({
abi,
functionName: 'mint',
args: [69420n],
chainId: 'eip155:10',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther(inputText)
})
})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.contractfunction constructs a well-formed JSON response 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.
app.frame('/', (c) => {
return c.res({
action: '/finish',
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Perform a transaction
</div>
),
intents: [
<Button.Transaction target="/send-ether">Send Ether</Button.Transaction>,
<Button.Transaction target="/mint">Mint</Button.Transaction>,
]
})
})
app.frame('/finish', (c) => {
const { transactionId } = c
return c.res({
image: (
<div style={{ color: 'white', display: 'flex', fontSize: 60 }}>
Transaction ID: {transactionId}
</div>
)
})
})
app.transaction('/send-ether', (c) => {
// Send transaction response.
return c.send({
chainId: 'eip155:10',
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B',
value: parseEther('1'),
})
})
app.transaction('/mint', (c) => {
// Contract transaction response.
return c.contract({
abi,
chainId: 'eip155:10',
functionName: 'mint',
args: [69420n],
to: '0xd2135CfB216b74109775236E36d4b433F1DF507B'
})
})5. Bonus: Learn the API
You can learn more about the transaction APIs here: