Validate Frames & Actions Message
Airstack provides an easy-to-use Validation API to validate your Farcaster Frames and Actions with Farcaster Hubs.
Pre-requisites
To access the Airstack Validation API, make sure that you have your Airstack API key.
For access to the Airstack API key, you'll need to hold at least 1 /airstack Channel Fan Token in either your Farcaster custodial wallet or one of your Farcaster verified wallets AND make sure that you have connected one of those wallet or your Farcaster account to your Airstack account.
If you don't have it yet, you can buy it here.
Methods
Depending on the programming language and the framework you are using, you can easily integrate the Validation API to your Frames and Actions to earn Moxie Everyday Rewards.
To determine the best method, follow the flowchart below from top to bottom for guidance:
Method #1: Frog Framework Integration
If you are using the Frog Framework, validation logic is embedded so you can instead set the hubs config to validate with the Airstack Validation API by providing the Hub API URL and the Airstack API key:
- TypeScript
- JavaScript
import { Frog } from "frog";
const app = new Frog({
hub: {
apiUrl: "https://hubs.airstack.xyz",
fetchOptions: {
headers: {
"x-airstack-hubs": "YOUR_AIRSTACK_API_KEY",
},
},
},
});
const { Frog } = require("frog");
const app = new Frog({
hub: {
apiUrl: "https://hubs.airstack.xyz",
fetchOptions: {
headers: {
"x-airstack-hubs": "YOUR_AIRSTACK_API_KEY",
},
},
},
});
Once you have added the code snippets above, all the Frames and Actions messages will automatically be validated with the Airstack Validation API.
Method #2: Frames.js Framework Integration
If you are using the Frames.js Framework, you can use the farcasterHubContext
middleware to set the hubs config to validate with the Airstack Validation API by providing the Hub API URL and the Airstack API key:
- TypeScript
- JavaScript
import { farcasterHubContext } from "frames.js/middleware";
const frames = createFrames({
middleware: [
farcasterHubContext({
...(process.env.NODE_ENV === "production"
? {
hubHttpUrl: "https://hubs.airstack.xyz",
hubRequestOptions: {
headers: {
"x-airstack-hubs": process.env.AIRSTACK_API_KEY as string,
},
},
}
: {
hubHttpUrl: "http://localhost:3010/hub",
}),
}),
],
});
const { farcasterHubContext } = require("frames.js/middleware");
const frames = createFrames({
middleware: [
farcasterHubContext({
...(process.env.NODE_ENV === "production"
? {
hubHttpUrl: "https://hubs.airstack.xyz",
hubRequestOptions: {
headers: {
"x-airstack-hubs": process.env.AIRSTACK_API_KEY as string,
},
},
}
: {
hubHttpUrl: "http://localhost:3010/hub",
}),
}),
],
});
Once you have added the code snippets above, all the Frames and Actions messages will automatically be validated with the Airstack Validation API.
Method #3: Airstack Frames SDK
- TypeScript
- JavaScript
You can validate your Farcaster Frames and Actions using the validateFramesMessage
function from the Airstack Frames SDK:
import {
validateFramesMessage,
ValidateFramesMessageInput,
ValidateFramesMessageOutput,
} from "@airstack/frames";
try {
// Your Frames Signature Packet from the request body
const body: ValidateFramesMessageInput = {
untrustedData: {
fid: 289309,
url: "https://sample.frames",
messageHash: "0xabc",
timestamp: 1709198011100,
network: 1,
buttonIndex: 1,
castId: {
fid: 289309,
hash: "0x0000000000000000000000000000000000000001",
},
},
trustedData: {
messageBytes:
"0a61080d109dd41118d0c9c72f20018201510a3168747470733a2f2f70656c6963616e2d666f6e642d64697374696e63746c792e6e67726f6b2d667265652e6170702f6f6710011a1a089dd4111214000000000000000000000000000000000000000112146357261fa893e4be85f78178babaca876f9a1fac18012240d1ed649964018377641a78638f0c19d3c346c1eb1a47e856c0fcd87d3fc72ff98172f939fc18ffdd16af746144279e6debb3f4913f491c69d22f6703e554510a280132200295183aaa021cad737db7ddbc075964496ece1c0bcc1009bdae6d1799c83cd4",
},
};
const res: ValidateFramesMessageOutput = await validateFramesMessage(body);
} catch (error) {
console.error(error);
}
const { validateFramesMessage } = require("@airstack/frames");
try {
// Your Frames Signature Packet from the request body
const body = {
untrustedData: {
fid: 289309,
url: "https://sample.frames",
messageHash: "0xabc",
timestamp: 1709198011100,
network: 1,
buttonIndex: 1,
castId: {
fid: 289309,
hash: "0x0000000000000000000000000000000000000001",
},
},
trustedData: {
messageBytes:
"0a61080d109dd41118d0c9c72f20018201510a3168747470733a2f2f70656c6963616e2d666f6e642d64697374696e63746c792e6e67726f6b2d667265652e6170702f6f6710011a1a089dd4111214000000000000000000000000000000000000000112146357261fa893e4be85f78178babaca876f9a1fac18012240d1ed649964018377641a78638f0c19d3c346c1eb1a47e856c0fcd87d3fc72ff98172f939fc18ffdd16af746144279e6debb3f4913f491c69d22f6703e554510a280132200295183aaa021cad737db7ddbc075964496ece1c0bcc1009bdae6d1799c83cd4",
},
};
const res = await validateFramesMessage(body);
} catch (error) {
console.error(error);
}
This snippet should be added as the first few lines on your Frames or Actions API endpoint before proccessing further. Once the message is validated, you will receive a response with the validation result as follows:
{
"isValid": true,
"message": {
"data": {
"type": 13,
"fid": 289309,
"timestamp": 99738832,
"network": 1,
"castAddBody": undefined,
"castRemoveBody": undefined,
"reactionBody": undefined,
"verificationAddAddressBody": undefined,
"verificationRemoveBody": undefined,
"userDataBody": undefined,
"linkBody": undefined,
"usernameProofBody": undefined,
"frameActionBody": {
"url": [
104, 116, 116, 112, 115, 58, 47, 47, 112, 101, 108, 105, 99, 97, 110,
45, 102, 111, 110, 100, 45, 100, 105, 115, 116, 105, 110, 99, 116,
108, 121, 46, 110, 103, 114, 111, 107, 45, 102, 114, 101, 101, 46, 97,
112, 112, 47, 111, 103
],
"buttonIndex": 1,
"castId": {
"fid": 289309,
"hash": [
211, 29, 52, 211, 77, 52, 211, 77, 52, 211, 77, 52, 211, 77, 52,
211, 77, 52, 211, 77, 52, 211, 77, 52, 211, 77, 52, 211, 77, 52, 211
]
},
"inputText": [],
"state": [],
"transactionId": []
}
},
"hash": [
211, 30, 183, 231, 189, 186, 213, 246, 188, 247, 119, 184, 109, 239, 57,
127, 191, 53, 239, 198, 218, 109, 167, 26, 243, 190, 159, 245, 173, 95,
105
],
"hashScheme": 1,
"signature": [
209, 237, 100, 153, 100, 1, 131, 119, 100, 26, 120, 99, 143, 12, 25, 211,
195, 70, 193, 235, 26, 71, 232, 86, 192, 252, 216, 125, 63, 199, 47, 249,
129, 114, 249, 57, 252, 24, 255, 221, 22, 175, 116, 97, 68, 39, 158, 109,
235, 179, 244, 145, 63, 73, 28, 105, 210, 47, 103, 3, 229, 84, 81, 10
],
"signatureScheme": 1,
"signer": [
211, 29, 54, 247, 157, 124, 221, 166, 154, 211, 109, 92, 105, 222, 247,
237, 214, 251, 117, 214, 220, 211, 190, 125, 235, 142, 61, 233, 231, 30,
213, 205, 27, 113, 205, 116, 211, 214, 221, 105, 238, 157, 215, 191, 125,
115, 205, 220, 119
],
"dataBytes": undefined
}
}
If isValid
is true
, it indicates that the message is valid on the Hubs and you can proceed. Otherwise, you should throw and error.
Method #4: Direct API Call
To validate your Frames and Actions messages, you can use the following query:
- Query
- Variable
- Response
query MyQuery(
$messageBytes: String!
) {
FarcasterValidateFrameMessage(
input: {filter: {messageBytes: $messageBytes}}
) {
isValid
message {
data {
fid
frameActionBody {
buttonIndex
castId {
fid
hash
}
inputText
state
}
}
}
interactedByFid
interactedBy {
profileName
}
castedByFid
castedBy {
profileName
}
}
}
{
"messageBytes": "0a61080d109dd41118d0c9c72f20018201510a3168747470733a2f2f70656c6963616e2d666f6e642d64697374696e63746c792e6e67726f6b2d667265652e6170702f6f6710011a1a089dd4111214000000000000000000000000000000000000000112146357261fa893e4be85f78178babaca876f9a1fac18012240d1ed649964018377641a78638f0c19d3c346c1eb1a47e856c0fcd87d3fc72ff98172f939fc18ffdd16af746144279e6debb3f4913f491c69d22f6703e554510a280132200295183aaa021cad737db7ddbc075964496ece1c0bcc1009bdae6d1799c83cd4"
}
{
"data": {
"FarcasterValidateFrameMessage": {
"isValid": true,
"message": {
"data": {
"fid": 289309,
"frameActionBody": {
"buttonIndex": 1,
"castId": {
"fid": 289309,
"hash": "0x0000000000000000000000000000000000000001"
},
"inputText": "",
"state": ""
}
}
},
"interactedByFid": 289309,
"interactedBy": {
"profileName": "kaushal"
},
"castedByFid": 289309,
"castedBy": {
"profileName": "kaushal"
}
}
}
}
graphql-request
library:- TypeScript
- JavaScript
import { gql, GraphQLClient } from "graphql-request";
import { config } from "dotenv";
config();
const graphQLClient = new GraphQLClient(
"https://api.airstack.xyz/graphql"
);
const query = gql`
query MyQuery(
$messageBytes: String!
) {
FarcasterValidateFrameMessage(
input: {filter: {messageBytes: $messageBytes}}
) {
isValid
message {
data {
fid
frameActionBody {
buttonIndex
castId {
fid
hash
}
inputText
state
}
}
}
interactedByFid
interactedBy {
profileName
}
castedByFid
castedBy {
profileName
}
}
}
`;
const variable = {
"messageBytes": "0a61080d109dd41118d0c9c72f20018201510a3168747470733a2f2f70656c6963616e2d666f6e642d64697374696e63746c792e6e67726f6b2d667265652e6170702f6f6710011a1a089dd4111214000000000000000000000000000000000000000112146357261fa893e4be85f78178babaca876f9a1fac18012240d1ed649964018377641a78638f0c19d3c346c1eb1a47e856c0fcd87d3fc72ff98172f939fc18ffdd16af746144279e6debb3f4913f491c69d22f6703e554510a280132200295183aaa021cad737db7ddbc075964496ece1c0bcc1009bdae6d1799c83cd4"
}
const headers = {
"Authorization": process.env.AIRSTACK_API_KEY as string,
}
try {
const data = await graphQLClient.request(query, variable, headers);
console.log(data);
} catch (e) {
throw new Error(e);
}
const { gql, GraphQLClient } = require("graphql-request");
const { config } = require("dotenv");
config();
const graphQLClient = new GraphQLClient(
"https://api.airstack.xyz/graphql"
);
const query = gql`
query MyQuery(
$messageBytes: String!
) {
FarcasterValidateFrameMessage(
input: {filter: {messageBytes: $messageBytes}}
) {
isValid
message {
data {
fid
frameActionBody {
buttonIndex
castId {
fid
hash
}
inputText
state
}
}
}
interactedByFid
interactedBy {
profileName
}
castedByFid
castedBy {
profileName
}
}
}
`;
const variable = {
"messageBytes": "0a61080d109dd41118d0c9c72f20018201510a3168747470733a2f2f70656c6963616e2d666f6e642d64697374696e63746c792e6e67726f6b2d667265652e6170702f6f6710011a1a089dd4111214000000000000000000000000000000000000000112146357261fa893e4be85f78178babaca876f9a1fac18012240d1ed649964018377641a78638f0c19d3c346c1eb1a47e856c0fcd87d3fc72ff98172f939fc18ffdd16af746144279e6debb3f4913f491c69d22f6703e554510a280132200295183aaa021cad737db7ddbc075964496ece1c0bcc1009bdae6d1799c83cd4"
}
const headers = {
"Authorization": process.env.AIRSTACK_API_KEY as string,
}
try {
const data = await graphQLClient.request(query, variable, headers);
console.log(data);
} catch (e) {
throw new Error(e);
}
The example provided here is using TypeScript and JavaScript. However, you can make the same GraphQL call in any other programming language that you are using.
Developer Support
If you have any questions or need help with other use cases, feel free to join the /airstack Warpcast channel and ask your questions there.
Our team is always ready to help you with any questions you may have.