Skip to content

Commit

Permalink
Merge pull request #41 from subspace/40-add-a-farcasterwarpcast-flow-…
Browse files Browse the repository at this point in the history
…to-request-token-frame

Add a farcaster&warpcast flow to request token frame
  • Loading branch information
marc-aurele-besner authored May 28, 2024
2 parents 73d83e7 + a95e2d9 commit 28f0df8
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 5 deletions.
Binary file added web-app/public/images/share-final.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web-app/public/images/share-request-error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web-app/public/images/share.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion web-app/src/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,21 @@ const author = 'Subspace Network'
const twitter = '@NetworkSubspace'
const icon = 'logo.png'
const svgIcon = 'logo.svg'
const shareImage = 'share.png'
const shareImage = url + '/images/share.png'
const request_url = url + '/api/requestTokens'

export const metadata = {
title: titleDefault,
url,
description,
keywords,
author,
twitter,
icon,
svgIcon,
shareImage,
request_url
}

const Header = ({ title = titleDefault }) => {
return (
Expand All @@ -35,6 +49,17 @@ const Header = ({ title = titleDefault }) => {
<meta name='og:site_name' content={title} />
<meta name='og:description' content={description} />

{/* Frames Interactive metadata */}
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content={shareImage} />
<meta property='fc:frame:image:aspect_ratio' content='1.91:1' />
<meta property='fc:frame:post_url' content={request_url} />
<meta property={`fc:frame:button:1`} content={'Request Nova Testnet Token'} />
<meta property={`fc:frame:button:1:action`} content={'post'} />
<meta property={`fc:frame:button:1:target`} content={request_url} />
{/* <meta property={`fc:frame:button:1`} content={'View transaction'} />
<meta property={`fc:frame:button:2`} content={'View your wallet'} /> */}

<link rel='manifest' href='/manifest.json' />
<link rel='mask-icon' color='#000000' href={svgIcon} />

Expand Down
114 changes: 110 additions & 4 deletions web-app/src/pages/api/requestTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { JsonFragment } from '@ethersproject/abi'
import { Contract, Wallet, providers } from 'ethers'
import { Client, FaunaHttpErrorResponseContent, query as faunaQuery } from 'faunadb'
import { NextApiRequest, NextApiResponse } from 'next'
import { metadata } from '../../config'
import { contracts } from '../../constants/contracts'
import { networks } from '../../constants/networks'
import { networks, nova } from '../../constants/networks'
import { createStats, findStats, updateStats } from '../../utils'

type AccountType = 'github' | 'discord'
type AccountType = 'github' | 'discord' | 'farcaster'

const faunaDbClient = new Client({
secret: process.env.FAUNA_DB_SECRET || '',
Expand Down Expand Up @@ -34,7 +35,7 @@ export const findRequest = async (accountType: AccountType, accountId: string, r
return true
})
.catch((error: FaunaHttpErrorResponseContent) => {
console.log('error', error)
console.error('error', error)
})
}

Expand All @@ -61,7 +62,7 @@ export const saveRequest = async (
})
)
.catch((error: FaunaHttpErrorResponseContent) => {
console.log('error', error)
console.error('error', error)
})
}

Expand All @@ -82,6 +83,38 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const REQUEST_DATE = CURRENT_TIME.toISOString().slice(0, 16) // Minutes precision
const STATS_DATE = CURRENT_TIME.toISOString().slice(0, 10) // Daily precision

// Farcaster Faucet Frame
let isFarcasterFrame = false
if (req.body && req.body.trustedData && req.body.trustedData.messageBytes) {
isFarcasterFrame = true

const response = await fetch('https://api.neynar.com/v2/farcaster/frame/validate', {
headers: {
api_key: 'NEYNAR_ONCHAIN_KIT',
accept: 'application/json',
'content-type': 'application/json'
},
method: 'POST',
body: JSON.stringify({
message_bytes_in_hex: req.body.trustedData.messageBytes
})
})

const data = await response.json().then((res) => {
return res
})

// Override request body with Farcaster Faucet Frame data
req.body.chainId = nova.id
if (data.action.interactor.verified_addresses.eth_addresses.length > 0) {
req.body.address = data.action.interactor.verified_addresses.eth_addresses[0]
} else {
req.body.address = data.action.interactor.custody_address
}
req.body.accountType = 'farcaster'
req.body.accountId = data.action.interactor.fid
}

if (!process.env.PRIVATE_KEY) throw new Error('Missing PRIVATE_KEY env var')
const PRIVATE_KEY = process.env.PRIVATE_KEY
try {
Expand Down Expand Up @@ -109,6 +142,45 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
const txResponse = await minterWallet.sendTransaction(tx)
await saveRequest(address, accountType, accountId, REQUEST_DATE, txResponse.hash)
await incrementFaucetRequestsCount(address, accountType, STATS_DATE)

if (isFarcasterFrame) {
return res.status(200).send(`<!DOCTYPE html>
<html>
<head>
<meta charSet='utf-8' />
<meta name='language' content='english' />
<meta httpEquiv='content-type' content='text/html' />
<meta name='author' content='${metadata.author}' />
<meta name='designer' content='${metadata.author}' />
<meta name='publisher' content='${metadata.author}' />
<title>${metadata.title}</title>
<meta name='description' content='${metadata.description}' />
<meta name='keywords' content='${metadata.keywords}' />
<meta name='robots' content='index,follow' />
<meta name='distribution' content='web' />
<meta name='og:title' content='${metadata.title}' />
<meta name='og:type' content='site' />
<meta name='og:url' content='${metadata.url}' />
<meta name='og:image' content='${metadata.url}/images/share-final.png' />
<meta name='og:site_name' content='${metadata.title}' />
<meta name='og:description' content='${metadata.description}' />
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content='${metadata.url}/images/share-final.png' />
<meta property='fc:frame:image:aspect_ratio' content='1.91:1' />
<meta property='fc:frame:post_url' content='request_url' />
<meta property='fc:frame:button:1' content='View transaction' />
<meta property='fc:frame:button:1:action' content='link' />
<meta property='fc:frame:button:1:target' content='${nova.blockExplorers?.default.url}/tx/${txResponse.hash}' />
<meta property='fc:frame:button:2' content='View your wallet' />
<meta property='fc:frame:button:2:action' content='link' />
<meta property='fc:frame:button:2:target' content='${nova.blockExplorers?.default.url}/address/${address}' />
</head>
</html>`)
}
res.status(200).json({
message: 'Success',
txResponse
Expand All @@ -122,6 +194,40 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
console.error('Error requesting token', error)
if (isFarcasterFrame) {
return res.status(200).send(`<!DOCTYPE html>
<html>
<head>
<meta charSet='utf-8' />
<meta name='language' content='english' />
<meta httpEquiv='content-type' content='text/html' />
<meta name='author' content='${metadata.author}' />
<meta name='designer' content='${metadata.author}' />
<meta name='publisher' content='${metadata.author}' />
<title>${metadata.title}</title>
<meta name='description' content='${metadata.description}' />
<meta name='keywords' content='${metadata.keywords}' />
<meta name='robots' content='index,follow' />
<meta name='distribution' content='web' />
<meta name='og:title' content='${metadata.title}' />
<meta name='og:type' content='site' />
<meta name='og:url' content='${metadata.url}' />
<meta name='og:image' content='${metadata.url}/images/share-request-error.png' />
<meta name='og:site_name' content='${metadata.title}' />
<meta name='og:description' content='${metadata.description}' />
<meta property='fc:frame' content='vNext' />
<meta property='fc:frame:image' content='${metadata.url}/images/share-request-error.png' />
<meta property='fc:frame:image:aspect_ratio' content='1.91:1' />
<meta property='fc:frame:post_url' content='request_url' />
<meta property='fc:frame:button:1' content='Try again' />
<meta property='fc:frame:button:1:action' content='post' />
<meta property='fc:frame:button:1:target' content='${metadata.url}/api/requestTokens' />
</head>
</html>`)
}
res.status(400).json({
message: 'Error',
error: JSON.stringify(error)
Expand Down

0 comments on commit 28f0df8

Please sign in to comment.