Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ethereum.org email support #39

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
import { isAddress } from "viem";
import z from "zod";
import { isAddress } from 'viem';
import z from 'zod';

export const SendOtpSchema = z.object({
email: z.string().email().endsWith("@pse.dev").refine(email => !email.includes('+'), {
message: "Email must not contain '+'"
}),
email: z
.string()
.email()
.refine(
(email) => {
return (
(email.endsWith('@pse.dev') || email.endsWith('@ethereum.org')) && !email.includes('+')
);
},
{
message: "Email must be from '@pse.dev' or '@ethereum.org' and must not contain '+'"
}
)
});

export type SendOtp = z.infer<typeof SendOtpSchema>;

export const VerifyOtpSchema = z.object({
email: z.string()
email: z
.string()
.email()
.endsWith("@pse.dev")
.refine(email => !email.includes('+'), {
message: "Email must not contain '+'"
}),
.refine(
(email) => {
return (
(email.endsWith('@pse.dev') || email.endsWith('@ethereum.org')) && !email.includes('+')
);
},
{
message: "Email must be from '@pse.dev' or '@ethereum.org' and must not contain '+'"
}
),
otp: z.number().int().gte(100000).lte(999999),
address: z.string().refine(isAddress, {
message: "Invalid address",
}),
message: 'Invalid address'
})
});

export type VerifyOtp = z.infer<typeof VerifyOtpSchema>;
68 changes: 34 additions & 34 deletions packages/interface/src/features/signup/components/RegisterEmail.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,62 @@
import { Dispatch, SetStateAction, useState } from "react";
import { toast } from "sonner";
import { Dispatch, SetStateAction, useState } from "react"
import { toast } from "sonner"

import { config } from "~/config";
import { Form, FormControl, FormSection } from "~/components/ui/Form";
import { Input } from "~/components/ui/Input";
import { EmailFieldSchema, EmailField } from "../types";
import { Button } from "~/components/ui/Button";
import { Spinner } from "~/components/ui/Spinner";
import { config } from "~/config"
import { Form, FormControl, FormSection } from "~/components/ui/Form"
import { Input } from "~/components/ui/Input"
import { EmailFieldSchema, EmailField } from "../types"
import { Button } from "~/components/ui/Button"
import { Spinner } from "~/components/ui/Spinner"

interface IRegisterEmailProps {
emailField:
| {
email: string;
}
| undefined;
| {
email: string
}
| undefined
setEmail: Dispatch<
SetStateAction<
| {
email: string;
}
email: string
}
| undefined
>
>;
>
}

const RegisterEmail = ({
emailField,
setEmail,
}: IRegisterEmailProps): JSX.Element => {
const [registering, setRegistering] = useState(false);
const [registering, setRegistering] = useState(false)

const registerEmail = async (emailField: EmailField) => {
try {
setRegistering(true);
setRegistering(true)
const response = await fetch(`${config.backendUrl}/send-otp`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(emailField),
});
const json = await response.json();
})
const json = await response.json()

if (!response.ok) {
console.log(response.status);
console.error(json);
toast.error((json.errors && json.errors[0]) ?? json.message);
console.log(response.status)
console.error(json)
toast.error((json.errors && json.errors[0]) ?? json.message)
} else {
setEmail(emailField);
toast.success(`OTP has been sent to ${emailField.email}`);
setEmail(emailField)
toast.success(`OTP has been sent to ${emailField.email}`)
}
} catch (error: any) {
console.error(error);
toast.error("An unexpected error occured registering your email");
console.error(error)
toast.error("An unexpected error occured registering your email")
} finally {
setRegistering(false);
setRegistering(false)
}
};
}

return (
<div className="w-72 sm:w-96">
Expand All @@ -65,16 +65,16 @@ const RegisterEmail = ({
onSubmit={(email) => registerEmail(email)}
>
<FormSection
description="Please register with your 'pse.dev' email."
description="Please register with your 'pse.dev' or 'ethereum.org' email."
title="Register"
>
<FormControl
required
hint="This is your 'pse.dev' email address"
hint="This is your 'pse.dev' or 'ethereum.org' email address"
label="Email address"
name="email"
>
<Input placeholder="[email protected]" />
<Input placeholder="[email protected] | [email protected]" />
</FormControl>
<Button
suppressHydrationWarning
Expand All @@ -87,7 +87,7 @@ const RegisterEmail = ({
</FormSection>
</Form>
</div>
);
};
)
}

export default RegisterEmail;
export default RegisterEmail
116 changes: 58 additions & 58 deletions packages/interface/src/features/signup/components/VerifyOtp.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,45 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { toast } from "sonner";
import { Address, encodeAbiParameters, parseAbiParameters } from "viem";
import { publicClient } from "~/utils/permissionless";
import { Identity } from "@semaphore-protocol/core";
import SemaphoreAbi from "~/utils/Semaphore.json";

import { config, semaphore } from "~/config";
import { Form, FormControl, FormSection } from "~/components/ui/Form";
import { Input } from "~/components/ui/Input";
import { OtpFieldSchema, OtpField } from "../types";
import { Button } from "~/components/ui/Button";
import useSmartAccount from "~/hooks/useSmartAccount";
import { getSemaphoreProof } from "~/utils/semaphore";
import { useMaci } from "~/contexts/Maci";
import { useEthersSigner } from "~/hooks/useEthersSigner";
import { Spinner } from "~/components/ui/Spinner";
import { useState } from "react"
import { useRouter } from "next/router"
import { toast } from "sonner"
import { Address, encodeAbiParameters, parseAbiParameters } from "viem"
import { publicClient } from "~/utils/permissionless"
import { Identity } from "@semaphore-protocol/core"
import SemaphoreAbi from "~/utils/Semaphore.json"

import { config, semaphore } from "~/config"
import { Form, FormControl, FormSection } from "~/components/ui/Form"
import { Input } from "~/components/ui/Input"
import { OtpFieldSchema, OtpField } from "../types"
import { Button } from "~/components/ui/Button"
import useSmartAccount from "~/hooks/useSmartAccount"
import { getSemaphoreProof } from "~/utils/semaphore"
import { useMaci } from "~/contexts/Maci"
import { useEthersSigner } from "~/hooks/useEthersSigner"
import { Spinner } from "~/components/ui/Spinner"

interface IVerifyOtpProps {
emailField: {
email: string;
};
email: string
}
}

const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
const { address, smartAccount, smartAccountClient } = useSmartAccount();
const signer = useEthersSigner({ client: smartAccountClient });
const { updateEligibility } = useMaci();
const router = useRouter();
const { address, smartAccount, smartAccountClient } = useSmartAccount()
const signer = useEthersSigner({ client: smartAccountClient })
const { updateEligibility } = useMaci()
const router = useRouter()

const [verifying, setVerifying] = useState(false);
const [verifying, setVerifying] = useState(false)

const verifyOtp = async (otpField: OtpField) => {
try {
setVerifying(true);
setVerifying(true)
if (!address) {
throw new Error("Smart account does not exist");
throw new Error("Smart account does not exist")
}

const { email: email } = emailField; // the component that can call this function only renders when the email exists
const { otp: otp } = otpField;
const { email: email } = emailField // the component that can call this function only renders when the email exists
const { otp: otp } = otpField

const response = await fetch(`${config.backendUrl}/verify-otp`, {
method: "POST",
Expand All @@ -51,62 +51,62 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
otp,
address,
}),
});
const json = await response.json();
})
const json = await response.json()

if (!response.ok) {
console.log(response.status);
console.error(json);
toast.error((json.errors && json.errors[0]) ?? json.message);
console.log(response.status)
console.error(json)
toast.error((json.errors && json.errors[0]) ?? json.message)
} else {
toast.success("OTP verified - now joining Semaphore group");
await joinSemaphoreGroup();
router.push("/signup");
toast.success("OTP verified - now joining Semaphore group")
await joinSemaphoreGroup()
router.push("/signup")
}
} catch (error: any) {
console.error(error);
toast.error("An unexpected error occured verifying the OTP");
console.error(error)
toast.error("An unexpected error occured verifying the OTP")
} finally {
setVerifying(false);
setVerifying(false)
}
};
}

const joinSemaphoreGroup = async () => {
if (!smartAccount || !smartAccountClient) {
throw new Error("Smart account does not exist");
throw new Error("Smart account does not exist")
}

const semaphoreIdentity = localStorage.getItem("semaphoreIdentity");
const semaphoreIdentity = localStorage.getItem("semaphoreIdentity")
if (!semaphoreIdentity || !signer) {
throw new Error("No Semaphore Identity or signer");
throw new Error("No Semaphore Identity or signer")
}

const identityCommitment = new Identity(semaphoreIdentity).commitment;
const identityCommitment = new Identity(semaphoreIdentity).commitment
const data = encodeAbiParameters(parseAbiParameters("uint"), [
semaphore.hatId,
]);
])

const { request } = await publicClient.simulateContract({
account: smartAccount,
address: semaphore.contracts.semaphore as Address,
abi: SemaphoreAbi.abi,
functionName: "gateAndAddMember",
args: [identityCommitment, data],
});
const txHash = await smartAccountClient.writeContract(request);
console.log("txHash", txHash);
})
const txHash = await smartAccountClient.writeContract(request)
console.log("txHash", txHash)

// TODO: (merge-ok) come up with a better fix
await new Promise((resolve) => setTimeout(resolve, 20000));
await new Promise((resolve) => setTimeout(resolve, 20000))

const proof = await getSemaphoreProof(
signer,
new Identity(semaphoreIdentity)
);
await updateEligibility(proof, address);
)
await updateEligibility(proof, address)

toast.success("Joined Semaphore group");
};
toast.success("Joined Semaphore group")
}

return (
<div className="w-72 sm:w-96">
Expand All @@ -118,7 +118,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
<FormControl
required
valueAsNumber
hint="Check your 'pse.dev' inbox for the OTP"
hint="Check your 'pse.dev' or 'ethereum.org' inbox for the OTP"
label="OTP"
name="otp"
>
Expand All @@ -135,7 +135,7 @@ const VerifyOtp = ({ emailField }: IVerifyOtpProps): JSX.Element => {
</FormSection>
</Form>
</div>
);
};
)
}

export default VerifyOtp;
export default VerifyOtp