Twitter account verification

How does the External Adapter verify a Twitter account?
Verifying a new Twitter account on the App
Verify a new Twitter account.
Accessing the VerifyTwitter contract on Polygonscan from the App
Access the updated VerifyTwitter contract from the Drawer.

How to make a request?

Let's take a look at the verification process. As well as it can be requested from the App, a verification can also be asked directly from the VerifyTwitter contract. It will only take a username as an input, and grab the Ethereum address from the interacting user.
Example input
A string representing the Twitter handle. The @ should not be provided.
The user approving the transaction - the address can't be supplied, as it will grab it from the transaction.
The process following the request is described in the preceding section (The verification process). We will now further investigate the way in which the External Adapter operates.

How does the EA perform the verification?

The External Adapter is written as a serverless function. Each time it is triggered with a request, the API server grabs the input parameters, performs the custom computation, and sends back its result (or an error, if anything happens in between). The full code is available here. Let's take a look at what occurs inside the scope of the createRequest function ; you will find comments directly in the code, to explain the process.
// The Twitter API client is initialized, using a Bearer Token
// It is a read-only key
const client = new TwitterApi(process.env.BEARER_TOKEN);
const roClient = client.readOnly;
// Custom parameters will be used by the External Adapter
// true: the parameter is required, if not provided it will
// throw an error
// false: the parameter is optional
const customParams = {
username: true,
address: true,
endpoint: false,
const createRequest = (input, callback) => {
// The Chainlink Validator helps validate the request data
const validator = new Validator(callback, input, customParams);
const jobRunID =;
// Validate input parameters
// The username specified by the user in the request parameters
const username = || 'TwitterDev';
// The Ethereum address of the user, grabbed when they
// interacted with the contract (msg.sender)
const address = || 'false';
// Specify the signature it should find in the tweets
const signature = `Verifying my Twitter account for ${address} with @usePromise!`;
// 1st STEP
// Get the user's ID from their username
.then((preRes) => {
// If the username doesn't exist, return early
// We don't want to throw an error by trying to read an
// undefined user tweets ; here, it will just return
// as if the user could not be verified
if (
(preRes.errors && preRes.errors[0].title.includes('Not Found')) ||
) {
const response = {
data: {
username: username,
result: false,
status: 200,
// Return that data to the Chainlink Node
callback(response.status, Requester.success(jobRunID, response));
} else {
// 2nd STEP
// If the user was found, we can proceed
// Get their 10 latest tweet
.userTimeline(, {
max_results: 10,
exclude: ['retweets', 'replies'],
// Then check if their 10 latest tweets include the signature
.then((res) => {
// If we are in development, use mocks instead of the actual tweets
// It allows us to test for a successful/failed signature
const tweets =
process.env.DEVELOPMENT === 'true' ? mockTweets :;
// In each one of the tweets, check if the signature is present
const result =
// Make sure there are tweets
!!tweets &&
// Check each one of the tweets, to see if it includes the signature
// (use lower case to ignore address case irregularities)
tweets.some((tweet) =>
// Pass the result to the response
const response = {
data: {
// Either true, if the signature was found, otherwise false
result: result,
// Return the address to be able to notify the user
// that the process is complete
status: 200,
// Then return the response data to the Chainlink node
callback(response.status, Requester.success(jobRunID, response));
.catch((error) => {
callback(500, Requester.errored(jobRunID, error));
.catch((err) => {
callback(500, Requester.errored(jobRunID, err));