Skip to content

Commit

Permalink
Merge pull request #58 from celo-org/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
dckesler authored Apr 4, 2023
2 parents 771f55e + 773b748 commit 39a6f16
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 36 deletions.
8 changes: 8 additions & 0 deletions apps/firebase/database-rules.bolt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Request {
status: RequestStatus, // Request Status enum
type: RequestType, // Request Type enum
tokens: RequestedTokenSet,
authLevel: AuthLevel | Null,
}

type Account {
Expand Down Expand Up @@ -46,6 +47,13 @@ type RequestType extends String {
}
}

type AuthLevel extends String {
validate() {
this === 'none' ||
this === 'authenticated'
}
}

/**
* Node Paths
*/
Expand Down
1 change: 0 additions & 1 deletion apps/firebase/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"transfer-funds": "ts-node scripts/transfer-funds.ts",
"cli": "ts-node scripts/cli.ts",
"build:rules": "firebase-bolt database-rules.bolt",
"keys:decrypt": "bash scripts/key_placer.sh decrypt",
"keys:encrypt": "bash scripts/key_placer.sh encrypt"
},
"dependencies": {
Expand Down
12 changes: 11 additions & 1 deletion apps/firebase/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ yargs
.option('faucetStableAmount', {
type: 'string',
})
.option('authenticatedGoldAmount', {
type: 'string',
})
.option('authenticatedStableAmount', {
type: 'string',
})
.option('bigFaucetSafeAmount', {
type: 'string',
description: "Amount of CELO to be sent to *bigFaucetSafeAddress* each time the script runs"
Expand All @@ -119,12 +125,14 @@ yargs

.option('deploy', {
type: 'boolean',
description: 'Wether to deploy functions after set config',
description: 'Whether to deploy functions after set config',
}),
(args) => {
setConfig(args.net, {
faucetGoldAmount: args.faucetGoldAmount,
faucetStableAmount: args.faucetStableAmount,
authenticatedGoldAmount: args.authenticatedGoldAmount,
authenticatedStableAmount: args.authenticatedStableAmount,
bigFaucetSafeAmount: args.bigFaucetSafeAmount,
bigFaucetSafeStablesAmount: args.bigFaucetSafeStablesAmount,
bigFaucetSafeAddress: args.bigFaucetSafeAddress,
Expand All @@ -145,6 +153,8 @@ function setConfig(network: string, config: Partial<NetworkConfig>) {
setIfPresent('node_url', config.nodeUrl),
setIfPresent('faucet_gold_amount', config.faucetGoldAmount),
setIfPresent('faucet_stable_amount', config.faucetStableAmount),
setIfPresent('authenticated_gold_amount', config.authenticatedGoldAmount),
setIfPresent('authenticated_stable_amount', config.authenticatedStableAmount),
setIfPresent('big_faucet_safe_address', config.bigFaucetSafeAddress),
setIfPresent('big_faucet_safe_amount', config.bigFaucetSafeAmount),
setIfPresent('big_faucet_safe_stables_amount', config.bigFaucetSafeStablesAmount),
Expand Down
6 changes: 4 additions & 2 deletions apps/firebase/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ export interface NetworkConfig {
nodeUrl: string
faucetGoldAmount: string
faucetStableAmount: string
authenticatedGoldAmount: string
authenticatedStableAmount: string
bigFaucetSafeAddress: string
bigFaucetSafeAmount: string
bigFaucetSafeStablesAmount: string
}



export function getNetworkConfig(net: string): NetworkConfig {
const allconfig = functions.config()
const config = allconfig.faucet
Expand All @@ -23,6 +23,8 @@ export function getNetworkConfig(net: string): NetworkConfig {
nodeUrl: config[net].node_url,
faucetGoldAmount: config[net].faucet_gold_amount,
faucetStableAmount: config[net].faucet_stable_amount,
authenticatedGoldAmount: config[net].authenticated_gold_amount,
authenticatedStableAmount: config[net].authenticated_stable_amount,
bigFaucetSafeAddress: config[net].big_faucet_safe_address,
bigFaucetSafeAmount: config[net].big_faucet_safe_amount,
bigFaucetSafeStablesAmount: config[net].big_faucet_safe_stables_amount,
Expand Down
26 changes: 22 additions & 4 deletions apps/firebase/src/database-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export interface AccountRecord {
locked: boolean
}

export enum AuthLevel {
none = "none",
authenticated = "authenticated"
}

export enum RequestStatus {
Pending = 'Pending',
Working = 'Working',
Expand All @@ -34,6 +39,7 @@ export interface RequestRecord {
dollarTxHash?: string
goldTxHash?: string
tokens?: RequestedTokenSet
authLevel: AuthLevel
}

enum RequestedTokenSet {
Expand Down Expand Up @@ -136,23 +142,35 @@ async function sendCelo(celo: CeloAdapter, to: string, amountInWei: string) {

function buildHandleFaucet(request: RequestRecord, snap: DataSnapshot, config: NetworkConfig) {
return async (account: AccountRecord) => {
const { nodeUrl, faucetGoldAmount, faucetStableAmount } = config
const { nodeUrl } = config
const { goldAmount, stableAmount } = getSendAmounts(request.authLevel, config)
const celo = new CeloAdapter({ nodeUrl, pk: account.pk })
await celo.init()
const ops: Array<Promise<unknown>> = []

if (request.tokens === 'Celo' || request.tokens === 'All' || request.tokens === undefined) {
ops.push(retryAsync(sendGold, 3, [celo, request.beneficiary, faucetGoldAmount, snap], 500))
ops.push(retryAsync(sendGold, 3, [celo, request.beneficiary, goldAmount, snap], 500))
}

if (request.tokens === 'Stables' || request.tokens === 'All' || request.tokens === undefined) {
ops.push(sendStableTokens(celo, request.beneficiary, faucetStableAmount, false, snap))
ops.push(sendStableTokens(celo, request.beneficiary, stableAmount, false, snap))
}

await Promise.all(ops)
}
}

function getSendAmounts(authLevel: AuthLevel, config: NetworkConfig): { goldAmount: string, stableAmount: string } {
switch(authLevel) {
case undefined:
case AuthLevel.none:
return { goldAmount: config.faucetGoldAmount, stableAmount: config.faucetStableAmount }
case AuthLevel.authenticated:
return { goldAmount: config.authenticatedGoldAmount, stableAmount: config.authenticatedStableAmount }
}

}

async function sendGold(celo: CeloAdapter, address: Address, amount: string, snap: DataSnapshot) {

const token = await celo.kit.contracts.getGoldToken()
Expand Down Expand Up @@ -254,7 +272,7 @@ enum ActionResult {
export class AccountPool {
constructor(
private db: database.Database,
private network: string,
public network: string,
private options: PoolOptions = {
getAccountTimeoutMS: 10 * SECOND,
retryWaitMS: 3000,
Expand Down
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"eslint-config-next": "13.1.1",
"firebase": "~9.16.0",
"next": "13.1.1",
"next-auth": "^4.20.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-google-recaptcha-v3": "^1.10.1",
Expand Down
7 changes: 5 additions & 2 deletions apps/web/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import type { AppProps } from 'next/app'
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3'
import 'styles/globals.css'
import { Analytics } from '@vercel/analytics/react';
import { SessionProvider } from 'next-auth/react'


export default function App({ Component, pageProps }: AppProps) {
export default function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
return (
<>
<GoogleReCaptchaProvider reCaptchaKey={process.env.NEXT_PUBLIC_RECAPTCHA_KEY as string}>
<Component {...pageProps} />
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
</GoogleReCaptchaProvider>
<Analytics/>
</>
Expand Down
44 changes: 44 additions & 0 deletions apps/web/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import NextAuth, { AuthOptions, Profile, Session } from 'next-auth'
import GithubProvider from 'next-auth/providers/github'

type ExtendedProfile = {
created_at?: string;
} & Profile

interface ExtendedUser {
created_at?: string;
name?: string | null;
email?: string | null;
image?: string | null;
}

type ExtendedSession = {
user?: ExtendedUser;
} & Session

export const authOptions: AuthOptions = {
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID || '',
clientSecret: process.env.GITHUB_SECRET || '',
})
],
callbacks: {
async jwt({ token, profile }) {
if (profile) {
const extProfile: ExtendedProfile = profile
token.user_created_at = extProfile.created_at
}
return token;
},
async session({ session, token, user }) {
const extSession: ExtendedSession = session;
if (extSession.user) {
extSession.user.created_at = token.user_created_at as string
}
return session;
}
}
}

export default NextAuth(authOptions)
27 changes: 22 additions & 5 deletions apps/web/pages/api/faucet.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { getServerSession } from 'next-auth/next'
import { authOptions } from "./auth/[...nextauth]"
import captchaVerify from 'src/captcha-verify'
import { FaucetAPIResponse, RequestStatus } from 'src/faucet-interfaces'
import { FaucetAPIResponse, RequestStatus, AuthLevel, Network, networks } from 'src/faucet-interfaces'
import { sendRequest } from 'src/firebase-serverside'


export default async function handler(
req: NextApiRequest,
res: NextApiResponse<FaucetAPIResponse>
) {
const { captchaToken, beneficiary, skipStables } = req.body

let authLevel = AuthLevel.none;
try {
const session = await getServerSession(req, res, authOptions)
if (session) {
authLevel = AuthLevel.authenticated
}
} catch(e) {
console.error("Authentication check failed", e)
}

const { captchaToken, beneficiary, skipStables, network } = req.body

if (!networks.includes(network)) {
res.status(400).json({ status: RequestStatus.Failed, message: `Invalid network: ${network}` })
return;
}

const captchaResponse = await captchaVerify(captchaToken)
if (captchaResponse.success) {
try {
const key = await sendRequest(beneficiary, skipStables)
const key = await sendRequest(beneficiary, skipStables, network, authLevel)
res.status(200).json({ status: RequestStatus.Pending, key })
} catch (error) {
console.error(error)
Expand All @@ -22,5 +40,4 @@ export default async function handler(
console.error("Faucet Failed due to Recaptcha", captchaResponse["error-codes"])
res.status(401).json({ status: RequestStatus.Failed, message: captchaResponse["error-codes"]?.toString() || 'unknown' })
}

}
11 changes: 8 additions & 3 deletions apps/web/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import Head from 'next/head'
import { isBalanceBelowPar } from 'src/balance'
import Logo from 'src/logo'
import RequestForm from 'src/request-form'
import { GitHubAuth } from 'src/github-auth'
import { SetupButton } from 'src/setup-button'
import styles from 'styles/Home.module.css'

export const inter = Inter({ subsets: ['latin'] })

interface Props {
Expand All @@ -26,8 +28,11 @@ export default function Home({isOutOfCELO}: Props) {
{isOutOfCELO && <header className={styles.notice}>
The Faucet is out of CELO for now. It will be topped up <a target="_blank" rel="noreferrer" href="https://explorer.celo.org/alfajores/epochs">within an hour</a>
</header>}
<div className={styles.logo}>
<Logo />
<div className={styles.topBar}>
<div className={styles.logo}>
<Logo />
</div>
<GitHubAuth />
</div>
</div>
<div className={styles.container}>
Expand Down Expand Up @@ -100,4 +105,4 @@ export async function getServerSideProps(): Promise<{props: Props}> {
return {
props: {isOutOfCELO}
}
}
}
13 changes: 11 additions & 2 deletions apps/web/src/faucet-interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
export const NETWORK = "alfajores"
export const NETWORK = 'alfajores'

export type Address = string
export type E164Number = string

export const networks = ['alfajores', 'baklava', 'cannoli'] as const
export type Network = typeof networks[number]

export enum RequestStatus {
Pending = "Pending",
Working = "Working",
Expand All @@ -14,13 +17,19 @@ export enum RequestType {
Faucet = "Faucet",
}

export enum AuthLevel {
none = "none",
authenticated = "authenticated"
}

export interface RequestRecord {
beneficiary: Address
status: RequestStatus
type: RequestType
dollarTxHash?: string
goldTxHash?: string
tokens?: RequestedTokenSet
authLevel: AuthLevel
}

export enum RequestedTokenSet {
Expand All @@ -35,4 +44,4 @@ export type FaucetAPIResponse = {
} | {
status: RequestStatus.Failed
message: string
}
}
14 changes: 9 additions & 5 deletions apps/web/src/firebase-serverside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import "firebase/compat/auth"
import "firebase/compat/database"
import {
Address,
NETWORK,
AuthLevel,
RequestedTokenSet,
RequestRecord,
RequestStatus,
RequestType
RequestType,
Network,
} from "./faucet-interfaces"
import firebaseConfig from "./firebase-config"

Expand Down Expand Up @@ -37,18 +38,21 @@ async function getDB(): Promise<firebase.database.Database> {

export async function sendRequest(
beneficiary: Address,
skipStables: boolean
skipStables: boolean,
network: Network,
authLevel: AuthLevel
) {
const newRequest: RequestRecord = {
beneficiary,
status: RequestStatus.Pending,
type: RequestType.Faucet,
tokens: skipStables ? RequestedTokenSet.Celo : RequestedTokenSet.Stables
tokens: skipStables ? RequestedTokenSet.Celo : RequestedTokenSet.Stables,
authLevel,
}

try {
const db = await getDB()
const ref: firebase.database.Reference = await db.ref(`${NETWORK}/requests`).push(newRequest)
const ref: firebase.database.Reference = await db.ref(`${network}/requests`).push(newRequest)
return ref.key
} catch (e) {
console.error(`Error while sendRequest: ${e}`)
Expand Down
Loading

1 comment on commit 39a6f16

@vercel
Copy link

@vercel vercel bot commented on 39a6f16 Apr 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.