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.transaction
route. - When the user presses the button in the App, the App will make a
POST
request to the.transaction
route. - 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
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.
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: (
<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-ether
route, and expose a "send ethereum to an address" interface to the App. - A "Mint" button that will call the
/mint
route, 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:10
where1
is Ethereum mainnet.to
: a recipient.value
: the amount of wei to send. We are usingparseEther
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 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.contract
function 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: