diff --git a/.github/workflows/foundry-npm.yml b/.github/workflows/foundry-npm.yml index eb5fb33..28cbcf9 100644 --- a/.github/workflows/foundry-npm.yml +++ b/.github/workflows/foundry-npm.yml @@ -15,6 +15,8 @@ jobs: with: version: nightly - name: NPM Clean Install - run: cd smart_contract && npm ci + run: npm ci + working-directory: smart_contract - name: Run Forge Test run: forge test + working-directory: smart_contract diff --git a/web-app/public/images/share-final.png b/web-app/public/images/share-final.png new file mode 100644 index 0000000..d4890bf Binary files /dev/null and b/web-app/public/images/share-final.png differ diff --git a/web-app/public/images/share-request-error.png b/web-app/public/images/share-request-error.png new file mode 100644 index 0000000..10defd0 Binary files /dev/null and b/web-app/public/images/share-request-error.png differ diff --git a/web-app/public/images/share.png b/web-app/public/images/share.png new file mode 100644 index 0000000..abe7183 Binary files /dev/null and b/web-app/public/images/share.png differ diff --git a/web-app/src/config.tsx b/web-app/src/config.tsx index 712fac0..c162894 100644 --- a/web-app/src/config.tsx +++ b/web-app/src/config.tsx @@ -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 ( @@ -35,6 +49,17 @@ const Header = ({ title = titleDefault }) => { + {/* Frames Interactive metadata */} + + + + + + + + {/* + */} + diff --git a/web-app/src/pages/api/requestTokens.ts b/web-app/src/pages/api/requestTokens.ts index 1975b07..fd6b950 100644 --- a/web-app/src/pages/api/requestTokens.ts +++ b/web-app/src/pages/api/requestTokens.ts @@ -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 { buildSlackStatsMessage, createStats, findStats, sendSlackMessage, updateStats } from '../../utils' -type AccountType = 'github' | 'discord' +type AccountType = 'github' | 'discord' | 'farcaster' const faunaDbClient = new Client({ secret: process.env.FAUNA_DB_SECRET || '', @@ -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) }) } @@ -61,7 +62,7 @@ export const saveRequest = async ( }) ) .catch((error: FaunaHttpErrorResponseContent) => { - console.log('error', error) + console.error('error', error) }) } @@ -100,6 +101,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 { @@ -127,6 +160,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(` + + + + + + + + + + ${metadata.title} + + + + + + + + + + + + + + + + + + + + + + + + + `) + } res.status(200).json({ message: 'Success', txResponse @@ -140,6 +212,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(` + + + + + + + + + + ${metadata.title} + + + + + + + + + + + + + + + + + + + + + `) + } res.status(400).json({ message: 'Error', error: JSON.stringify(error)