Twitter Campaigns is an A.I.-enabled on-chain DAO-tool aimed at crypto-familiar users where anyone can create, and participate in on-chain Twitter marketing campaigns. This Dapp Leverages Account-Abstraction, fiat on-ramps, gas-free transactions, and copy written by A.I.!
Twitter Campaigns is an on-chain A.I. enabled DAO-tool to create Twitter marketing campaigns that live on a smart-contract, taking DAO-tooling and social proof to a whole new level. The goal of this dapp is two-fold: To enable DAO's, or anybody to gain promotional social media posts through tokenized incentive, and to enable more non-crypto experts in participating in these incentives through FOMO and social-proof. As twitter is one of the most popular social networks in the world, we leverage the authentication and user accounts that already exist on Twitter and enable crypto functionality on top of it. The dapp UI is made up of 3 sections: 1. Account Summary, 2. Campaigns, and 3. Reward Logs.
The wallet is displayed in the account summary. This is generated using the Safe AuthKit sdk, which uses Web3Auth, Auth0 and twitter authentication under the hood. The balance is displayed in USD and MATIC testnet. As a bonus, when logged in, we display the twitter icon for that account.
Note: To fund the AA wallet, we use Biconomy's Transak SDK. This uses the same email connected to the twitter account making the on-ramp process that much easier. Although this doesn't really do anything on testnets, to accomplish simple testing we fund the AA wallet with Metamask manually. Although we manually fund for testing, pushing this app to production enabled anyone with a credit card to fund their AA wallet. According to Biconomy SDK docs, transak can also act as an off-ramp (although there are no docs or functionality present for this atm).
Users participate in campaigns by submitting a tweet to a specific campaign for token rewards. Campaign rules dictate the tweet must contain a "tweet string" or in our general use case, a hashtag. Once a tweet is checked and allowed to qualify, each "like" and "retweet" is worth X and Y number of tokens respectively. The user can claim this reward without paying any gas. Transaction to claim rewards is run in the backend by an admin wallet. This is to ensure each tweet submitted is authenticated and authored by the same user submitting it. The likes and retweets are saved in the CampaignManager smart contract under a "tweetId" to avoid duplicate rewards.
The idea of sponsoring the transaction for the user also comes from ease-of-use. Not everyone participating in campaigns will create them, so they shouldn't have to pay gas to claim their rewards. They have already done the work promoting the campaign by tweeting the specified hashtag!
To display the Campaigns, we use TheGraph subgraph deployed on mumbai. On the campaign card, we feature the creator or "owner" of the campaign. This gives the feeling of "social-proof".
Note: We did not focus on bot-checking or authenticating validity of the tweets likes and retweets. Theoretically we could use Twitters "impressions" metric or any other metric to create a more robust algorithm when rewarding participants.
To display the reward logs, we also use a TheGraph subgraph deployed on mumbai.
Notable mentions:
High level tech stack: Next.js (client/server), React, Node.js, Hardhat (smart contracts), TheGraph (subgraphs)
Smart contracts (hardhat):
We have one main smart contract, "CampaignManager". This smart contract stores everything to do with the campaigns and users who participate. We track campaign total rewards distributed, rewards left, user-campaign reward distribution and the stats associated with creating and managing campaigns and rewards. To create a campaign you call createCampaignNative
which emits a CampaignCreated
event for the subgraph. One parameter of special-note, is _ownerTwitterUserId
. We pass this into the contract exclusively for the subgraph. This way we can associate an owner directly with a campaign (see the "by @XXXXX" label on the campaign cards in the demo).
To participate and claim rewards, an admin calls claimRewardNativeTo
which emits the event RewardClaimed
. Users do not directly call claimRewardNativeTo
but instead a backend admin account is setup to call this method on their behalf. This is to ensure no exploits and reward farming occur. It is not gas efficient to check the tweet statistics inside solidity, also, we consider a balance between on-chain functionality and storage, and off-chain authentication when designing the smart contracts and overall systems around them. The off-chain advantage is to ensure the contract mechanics stay "fair" when operated by a trusted source, especially when it is open-source!
We store the previously rewarded likes and retweets statistics directly in the smart contract, and compare those values with the given ones when calling claimRewardNativeTo
to calculate the total number of rewards given out. We do not reward if there is insufficient rewards available, although a design choice this could easily change to reward the remaining reward supply to the user.
Smart contracts were deployed and tests written using hardhat and QuikNode RPCs.
Subgraph (TheGraph): The one subgraph we use is deployed on the same chain as CampaignManager.sol, Matic testnet Mumbai. We use this to display almost all of the UI on the dapp. Campaigns and RewardLogs are mapped to user wallets and fetched via the subgraph to display on the main page. We do some data transforming, such as including the twitter handle in the UI alongside other subgraph data.
Note: 4 hours before project submission deadline and up to the deadline itself, the subgraphs on mumbai stopped working. All of them. Multiple reports in EthGlobal and their own discord. Not sure why :/
Frontend (Next.js and Node.js and React): We tried to keep the frontend minimal and avoid complex crypto pitfalls, because of this we omit transaction hashes and add USD pricing beside MATIC token amounts. We opted to use MaterialUI for the general theme and UX. Pending transactions are represented as spinning circles and all errors are caught in snackbar popups and alerts, depending on the context. Success messages are clearly displayed. We opted to keep the UI "crypto newbie" friendly. The lack of transaction hashes is intentional.
The onboarding UI flow is controlled by Safe AuthKit. This in turn uses Web3Auth modal inside a wrapper class called "Web3AuthModalPack". We were unable to customize this to omit any other option other than Twitter login, but keep in mind that Twitter is the only accepted login method. To connect the Safe AuthKit to twitter authentication, we used a complex stack of tech: Web3Auth, Auth0 and a twitter developer app. Safe AuthKit uses Web3Auth which prompts the user to log into their Twitter account. This in turn talks to Auth0 and redirects to "openlogin" which in turn redirects to Web3Auth and our application.
More on the onboarding flow: We receive an "idToken" that is sent to the next.js backend api route we created called "/api/auth" that verifies the idToken against the users public_key. When this verification is complete we send a response to the frontend, and effectively we now have a users twitter account connected to a wallet! After authentication we have access to the users twitter userId, their profile image and email. This onboarding flow consisting of social-proof enables us to verify their twitter account when they submit reward claims on campaigns.
A cool hack: in addition to the idToken verification, the "/api/auth" endpoint returns an admin-wallet signed message containing the user-AA wallet and their twitter user Id. This is then re-served to any endpoint the user calls for validation and authentication (such as the claim rewards "/api/reward" endpoint). We check the signer of the message and verify that the user is allowed to use our endpoints! Easy wallet/twitter id authentication after login!
To create a campaign, user clicks "Create Campaign" after logging in. This opens a dialog box and lets the user fill out the options for a campaign. We feature AI to help fill out the text-based fields for the campaign! OpenAI is prompted and pre-set with a custom strategy, the user gives a "theme" prompt and our custom strategy handles formatting the theme into a json for us to use in the "api/openai" Next.js response.
To claim a reward on the frontend, a user must copy their tweet id (found in the tweets url when on the twitter website), into the campaign card and click "Claim Reward". This spins up a request to the backend via the api "/api/reward" and authenticates the users signature, then checks if they are eligible for rewards, if so the transaction to claim is sent through. Any success and warning messages pop up as snack-bars in the bottom left of the screen using the npm library "notistack".
Context and state tracking: Each major component (account summary, campaigns and reward logs) keep track of their own states. The userContext (in context/) keeps track of the user login info, and is shared between all components. This ensures we only write that code once, and any code pertaining to updating or receiving more information on the user lives there (such as the admin-signed signature, user provider and signer objects and boolean login-state flags).
When not logged in, items are omitted from the UI such as the account summary and any button that corresponds to executing a transaction.
The chosen style-sheet is the default Next.js style-sheet, with some tweaks such as light mode.
Backend (Next.js api routes):
We use a few api routes, such as:
"api/auth" - Used to authenticate a users "idToken" after they complete the initial part of the twitter onboarding/social-proof login flow via Safe AuthKit. We verify their idToken using JWK, connected to "openlogin" using the ES256 algorithm. Once their token is verified to be valid, we generate an admin-signed ethereum-compatible signature. In the signature message we store the users wallet and twitter user Id. This serves as the authentication token for the endpoint "api/reward".
"api/openai" - This endpoint is used to auto-fill the create campaign form, using a custom-built strategy prompt the user provides a "theme" prompt message, and alongside our strategy this AI generates a JSON with "title", "description" and "hashtag" for us to pre-fill the frontend form with.
"api/reward" - Called by users requesting to be rewarded for a tweet in a given campaign. This flow is as follows: 1) user provides tweet id to api 2) user signature authenticated 3) author_id on tweet is checked to be the same as requesting user 4) check the tweet includes the required "tweet string" (or "hashtag") 5) reward user by calling claimRewardNativeTo
on their behalf. Returns the total reward formatted for the frontend to display in a snackbar.
"api/tweetauthor" - Simple endpoint to call the twitter api and find a tweets author given a specific tweet id. Used to display the twitter handle on the reward logs cards.
"api/twitteruser" - Simple endpoint to call the twitter api and find the users twitter handle given their user id. Used by the frontend to display the current logged-in users handle and the campaign creators handle on the campaign cards.