🐦 Creating a Twitter Engagement Campaign

Reward your brand's Twitter fans with ERC-721

πŸ‘

Get Help

If you get stuck at any time, reach out to us on Discord or contact us via email at [email protected].

This guide will walk you through how to use multiple Co:Create services together to build a simple social engagement campaign with Co:Create.

In this guide, we'll cover how to use:

  1. The Users API to create users and give them wallets
  2. The ERC721 API to create a ERC-721 contract & mint a token to your user's wallet.

We'll assume that you're building your own community member application on top of Co:Create APIs.

Use Case

Imagine you're building on Co:Create, and one of your organizational objectives is to grow your brand's Twitter account following. In support of this, you've decided to incentivize community members with ERC721 NFTs that they receive after they follow your Twitter account.

An example of how this campaign might look within an application

An example of how this campaign might look within an application

User Journey

The user journey from the community member's perspective:

  1. The member signs up or logs into your brand’s application.
  2. the member connects their Twitter account to your brand’s back end when prompted.
  3. The member receives an ERC-721 token in their wallet.

This critical path represents four to five clicks. All those interactions happen inside your brand's app.

Reward campaign flow, simplified

The user journey


Campaign Workflow

Creating this campaign is quite straightforward. It is done in five steps:

  1. Creating the campaign's ERC-721 contract
  2. Creating users and their wallet
  3. Connecting users' Twitter account
  4. Querying Twitter API for users' follows
  5. Minting ERC-721 tokens to users' wallets

Creating ERC-721 Contract

πŸ“˜

Note:

The following explanation is about an ERC-721-based campaign. If you would like to build a ERC-20-based campaign, please refer to the Point Guide.

Deploying an ERC721 contract on the blockchain involves:

  1. Uploading the contract media to IPFS, in our case, the media is a street-art .png , but any common media type can be used for the NFT: gif, jpeg, mp4, etc.
  2. Uploading a metadata file specifying the media's characteristics: metadata files are json files specifying the media location on IPFS, its file format, name, etc...
  3. Deploying the ERC-721 contract on-chain: it's the smart contract that we'll use to mint tokens.

Uploading The ERC-721's Media to IPFS

You can find all the details of this step in this guide. Feel feel to jump back in when done!


Creating The ERC-721 Contract

We've now reached the point where we can deploy the ERC-721 contract to the blockchain. The ERC721 token standard. ERC-721 is the most used standard for representing ownership of non-fungible tokens (NFTs). It allows for broad interoperability within and between EVM-blockchains. This contract will be the one used to mint the ERC-721 tokens.

πŸ’‘

Contracts are OpenZeppelin-based

Co:Create uses OpenZeppelin as the base of all token contracts.

Co:Create streamlines the contract creation and deployment process, which translates into a single post call to the erc721 endpoint.

Example request (against the test environment):

curl --request POST \
     --url https://api.testcocrt.xyz/alpha/erc721 \
     --header 'Authorization: Bearer <YOUR_KEY_HERE>' \
     --data '
{
  "symbol": "STRT",
  "name": "street-coffee",
  "base_uri": "https://nftstorage.link/ipfs/bafybeiasv4llnkervuprzfte5sulodkzobcmpfc7ldwybdwiqq5ywbbstu/"
}
'
const sdk = require('api')('@cocreate/v0.0.1-alpha#4hennhmllg6z0khk');

sdk.auth('Bearer zpka_e6567441cbed457d9fb18e0b67f74ecd_57167fde');
sdk.collectibles721DeployNewCollectible({
  name: 'street-coffee',
  symbol: 'STRTK',
  base_uri: 'https://nftstorage.link/ipfs/bafybeiasv4llnkervuprzfte5sulodkzobcmpfc7ldwybdwiqq5ywbbstu/'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));
import requests

url = "https://api.testcocrt.xyz/alpha/erc721"

payload = {
    "name": "street-coffee",
    "symbol": "STRTK",
    "base_uri": "https://nftstorage.link/ipfs/bafybeiasv4llnkervuprzfte5sulodkzobcmpfc7ldwybdwiqq5ywbbstu/"
}
headers = {
    "accept": "application/json",
    "content-type": "application/json",
    "Authorization": "Bearer zpka_e6567441cbed457d9fb18e0b67f74ecd_57167fde"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)

Example response:

{
  "data": {
    "erc721_id": "1824b6fe-ca95-44ac-987f-35cd4c9e436d",
    "contract_address": "0x50523B67D46B87eEDB2546FF30DfAF4886524416"
  }
}

Response Fields:

  • The erc721_id field represents the Co:Create UUID representing the contract. We will use this address to mint ERC-721 tokens for our reward program members later in this tutorial.
  • The contract_address field represents the Polygon address of the contract on-chain.

πŸ“˜

Note:

You may add additional parameters to the request if you want to add a royalty fee structure, transfer restrictions, etc. You can find all of parameters in the ERC-721 creation API reference (you can try them live)


Creating Users And Their Wallet

We now have a collection deployed on-chain. Now, we need to allow community members to hold the ERC-721 they earn. We'll do that by creating a user within Co:Create, where they'll have a wallet automatically created on their behalf.

Creating a user consists of a single POST request to the /user endpoint. The user object that we are going to deploy has the following properties:

User KeyUser PropertyRequired
emailuser email: that's the primary keyYes
first_nameuser first nameNo
last_nameuser last nameNo
phone_numberuser phone numberNo
external_walletslist of external wallets controlled by the userNo
cocreate_wallet_addressCo:Create wallet address associated with the userNo

You can try these requests live in the Co:Create API Reference.

1curl --request POST \
     --url https://api.testcocrt.xyz/alpha/user \
     --header 'Authorization: Bearer <YOUR_API_KEY>' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "email": "[email protected]",
  "first_name": "Al",
  "last_name": "Abama",
  "phone_number": "1234567890",
  "phone_number": "1234567890",
}
const sdk = require("api")("@cocreate/v0.0.1-alpha#1xltvr9alfms0foz");

sdk.auth("Bearer <YOUR_API_KEY>");
sdk
  .userManagementCreateUser({
    email: "[email protected]",
    first_name: "Al",
    last_name: "Abama",
    phone_number: "1234567890",
  })
  .then(({ data }) => console.log(data))
  .catch((err) => console.error(err));
require 'uri'
require 'net/http'
require 'openssl'

url = URI("https://api.testcocrt.xyz/alpha/user")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["accept"] = 'application/json'
request["content-type"] = 'application/json'
request["Authorization"] = 'Bearer <YOUR_API_KEY>'
request.body = "{\"email\":\"[email protected]\",\"first_name\":\"Al\",\"last_name\":\"Abama\",\"phone_number\":\"1234567890\"}"
response = http.request(request)
puts response.read_body
import requests

url = "https://api.testcocrt.xyz/alpha/user"

payload = {"email": "[email protected]", "first_name": "Al", "last_name": "Abama", "phone_number": "1234567890"}
headers = {
    "accept": "application/json",
    "content-type": "application/json",
    "Authorization": "Bearer <YOUR_API_KEY>"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)

Example response:

{
  "data": {
    "cocreate_wallet_address": "0x0f7b2e40d81d7dbf47e214b92bb1d9e1b4ef4e29",
    "email": "[email protected]",
    "external_wallets": [
      "0x30391925ab782008febe2fbf05ea043ec966831c",
      "0x30391925ab782008febe2fbf05ea043ec966831d"
    ],
    "first_name": "Al",
    "last_name": "Abama",
    "phone_number": "1234567890"
  }
}

πŸ’‘

External wallets are supported

If your community members already have wallets, you can post their wallet address to the corresponding Co:Create user identity so that you can store it for use later.

The wallet addresses returned will be used in the following steps to mint the ERC-721 tokens. We want to keep them safe in a file.


Querying Twitter API to Verify Follows

We need to verifiably link our members' ids to their Twitter handles and query Twitter's API to confirm that they are following your brand's account. The easiest way is to have the community members connect their Twitter accounts to your app.

Here's a high-level overview of the steps we'll need to take:

  1. Redirect the user to Twitter's OAuth flow so that a member can authorize your app to access their Twitter account data.
  2. Query Twitter's API for follows to verify they're following the account.

Connecting Twitter Accounts

We need to verifiably link our members' ids to their Twitter handles and gain authorization to read their data. There are several ways to authorize your app to use Twitter APIs on your community members' behalf. One of them is to implement "log in with Twitter". Closer to the metal, two of Twitter's OAuth APIs can be used to implement a "connect with Twitter" module:

We won't go into further detail here as they are specific to Twitter integration. Once the authorization flow is complete, we will have a token verifying our user's ownership of the Twitter handle and authorizing read access to the account. This token should be stored somewhere on your server.

Verifying Follows

Once the user has verifiably linked their account and provided read authorization, the next step will be querying Twitter's API to verify follows.

The purpose of this step is to obtain a list of community member follows and match one of them with your brand's Twitter handle.

You can find more information about Twitter V2 API Follows endpoint in this post

Here's a cURL example of this request:

curl --location 'https://api.twitter.com/2/users/2244994945/following?max_results=5&access_token=QlRxNUQxamxhd2ZmME1EN2trTGs0dmtHaGRCOWhxUGtjSDFLX0I1OWVrZU1BOjE2ODA4MzMxNDI3NDk6MTowOmF0OjE' \
--header 'Cookie: guest_id=v1%3A168045197240635222; guest_id_ads=v1%3A168045197240635222; guest_id_marketing=v1%3A168045197240635222; personalization_id="v1_QX4gzD7d+weufCuJnUroWA=="'

Twitter will return a `user object from which we can retrieve data. Here is an example of how we can fetch whether or not a user is following an account:

// Fetch the users being followed by a specific account by ID
// https://developer.twitter.com/en/docs/twitter-api/users/follows/quick-start

const needle = require('needle');

// this is the ID for @TwitterDev
const userId = 2244994945;
const url = `https://api.twitter.com/2/users/${userId}/following`;
const bearerToken = process.env.BEARER_TOKEN;

const getFollowing = async () => {
    let users = [];
    let params = {
        "max_results": 1000,
        "user.fields": "created_at"
    }

    const options = {
        headers: {
            "User-Agent": "v2FollowingJS",
            "Authorization": `Bearer ${bearerToken}`
        }
    }

    let hasNextPage = true;
    let nextToken = null;
    console.log("Retrieving users this user is following...");
    while (hasNextPage) {
        let resp = await getPage(params, options, nextToken);
        if (resp && resp.meta && resp.meta.result_count && resp.meta.result_count > 0) {
            if (resp.data) {
                users.push.apply(users, resp.data);
            }
            if (resp.meta.next_token) {
                nextToken = resp.meta.next_token;
            } else {
                hasNextPage = false;
            }
        } else {
            hasNextPage = false;
        }
    }

    console.log(users);
    console.log(`Got ${users.length} users.`);

}

const getPage = async (params, options, nextToken) => {
    if (nextToken) {
        params.pagination_token = nextToken;
    }

    try {
        const resp = await needle('get', URL, params, options);

        if (resp.statusCode != 200) {
            console.log(`${resp.statusCode} ${resp.statusMessage}:\n${resp.body}`);
            return;
        }
        return resp.body;
    } catch (err) {
        throw new Error(`Request failed: ${err}`);
    }
}

getFollowing();
import requests
import os
import json

# To set your environment variables in your terminal, run the following line:
# export 'BEARER_TOKEN'='<your_bearer_token>'
bearer_token = os.environ.get("BEARER_TOKEN")


def create_url():
    # Replace with user ID below
    user_id = 2244994945
    return "https://api.twitter.com/2/users/{}/following".format(user_id)


def get_params():
    return {"user.fields": "created_at"}


def bearer_oauth(r):
    """
    The method is required by bearer token authentication.
    """

    r.headers["Authorization"] = f"Bearer {bearer_token}"
    r.headers["User-Agent"] = "v2FollowingLookupPython"
    return r


def connect_to_endpoint(url, params):
    response = requests.request("GET", url, auth=bearer_oauth, params=params)
    print(response.status_code)
    if response.status_code != 200:
        raise Exception(
            "Request returned an error: {} {}".format(
                response.status_code, response.text
            )
        )
    return response.json()


def main():
    url = create_url()
    params = get_params()
    json_response = connect_to_endpoint(url, params)
    print(json.dumps(json_response, indent=4, sort_keys=True))


if __name__ == "__main__":
    main()
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.net.URISyntaxException;
import java.util.ArrayList;

/*
 * Sample code to demonstrate the use of the v2 following lookup endpoint
 * */
public class FollowingLookupDemo {

  // To set your environment variables in your terminal, run the following line:
  // export 'BEARER_TOKEN'='<your_bearer_token>'

  public static void main(String args[]) throws IOException, URISyntaxException {
    final String bearerToken = System.getenv("BEARER_TOKEN");
    if (null != bearerToken) {
      // Replace with user ID below
      String response = getFollowing("2244994945", bearerToken);
      System.out.println(response);
    } else {
      System.out.println("There was a problem getting your bearer token. Please make sure you set the BEARER_TOKEN environment variable");
    }
  }

  /*
   * This method calls the v2 following lookup endpoint
   * */
  private static String getFollowing(String userId, String bearerToken) throws IOException, URISyntaxException {
    String tweetResponse = null;

    HttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(RequestConfig.custom()
            .setCookieSpec(CookieSpecs.STANDARD).build())
        .build();

    URIBuilder uriBuilder = new URIBuilder(String.format("https://api.twitter.com/2/users/%s/following", userId));
    ArrayList<NameValuePair> queryParameters;
    queryParameters = new ArrayList<>();
    queryParameters.add(new BasicNameValuePair("user.fields", "created_at"));
    uriBuilder.addParameters(queryParameters);

    HttpGet httpGet = new HttpGet(uriBuilder.build());
    httpGet.setHeader("Authorization", String.format("Bearer %s", bearerToken));
    httpGet.setHeader("Content-Type", "application/json");

    HttpResponse response = httpClient.execute(httpGet);
    HttpEntity entity = response.getEntity();
    if (null != entity) {
      tweetResponse = EntityUtils.toString(entity, "UTF-8");
    }
    return tweetResponse;
  }
}

Minting ERC-721 Tokens to Users

Now that our user has been verified as a follower of your brand on Twitter, we will reward them with an ERC-721 token. Or, in Web3 terms -- we'll mint an NFT for them.

Once again, this is done with a single call: a POST request to the /erc721/mint endpoint.

To do this, we'll need to provide the following parameters:

This is an example of a request to mint one ERC-721 for a user, but you can mint as many as you wish. Explore it further in the API Reference:

curl --request POST \
     --url https://api.testcocrt.xyz/alpha/erc721/mint \
     --header 'Authorization: Bearer zpka_e6567441cbed457d9fb18e0b67f74ecd_57167fde' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "mint_quantity": 1,
  "wallet_address": "0x0c040570aa12e202a5fa382b5b389dfc794a9f7b",
  "erc721_id": "1824b6fe-ca95-44ac-987f-35cd4c9e436d"
}
'
const sdk = require('api')('@cocreate/v0.0.1-alpha#1xltvr9alfms0foz');

sdk.auth('Bearer zpka_e6567441cbed457d9fb18e0b67f74ecd_57167fde');
sdk.collectibles721MintCollectible({
  mint_quantity: 1,
  wallet_address: '0x0c040570aa12e202a5fa382b5b389dfc794a9f7b',
  collectible_id: '1824b6fe-ca95-44ac-987f-35cd4c9e436d'
})
  .then(({ data }) => console.log(data))
  .catch(err => console.error(err));
import requests

url = "https://api.testcocrt.xyz/alpha/collectibles/mint"

payload = {
    "mint_quantity": 1,
    "wallet_address": "0x0c040570aa12e202a5fa382b5b389dfc794a9f7b",
    "collectible_id": "1824b6fe-ca95-44ac-987f-35cd4c9e436d"
}
headers = {
    "accept": "application/json",
    "content-type": "application/json",
    "Authorization": "Bearer zpka_e6567441cbed457d9fb18e0b67f74ecd_57167fde"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)

And this is the response we'll get back:

{
  "data": {
    "erc721_id": "1824b6fe-ca95-44ac-987f-35cd4c9e436d",
    "contract_address": "0x50523b67d46b87eedb2546ff30dfaf4886524416",
    "token_ids": [0],
    "wallet_address": "0x0c040570aa12e202a5fa382b5b389dfc794a9f7b"
  }
}

That's the end of this guide. You should now understand how to use the Users API and the ERC721 API to build a custom Twitter campaign, with the idea of extending them to other use cases.

πŸ‘

Get Help

If you get stuck at any time, reach out to us on Discord or contact us via email at [email protected].


What’s Next

Head on over to the API Reference