From 1076ebafadfb8d826eec81e4adb48d2ed5172846 Mon Sep 17 00:00:00 2001 From: Mehdi Torabi <46302001+mehdi-torabiv@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:17:58 +0300 Subject: [PATCH] Feat/close #43 (#44) * define auth api and use in attestation * add callback * fix callback page issue * update else state * setup attestation * add AuthenticationWrapper * fix custom auth with wallet * add attest integration * remove connectButton in app bar * update import * update import * update import * add protect route * update constant.d.ts * update ts.config * upd --- .gitignore | 4 +- package-lock.json | 26 +- package.json | 3 +- src/App.tsx | 136 ++- src/ProtectedRoute.tsx | 24 +- src/interfaces/index.ts | 4 + src/pages/Callback/Callback.tsx | 80 ++ src/pages/Callback/index.ts | 3 + .../Identifiers/Attestation/Attestation.tsx | 244 +++- src/pages/Identifiers/Identifiers.tsx | 47 +- src/router/index.tsx | 7 +- src/services/api/auth/index.ts | 8 + src/{ => services}/api/index.ts | 9 +- src/services/api/linking/index.ts | 15 + src/services/api/linking/query.ts | 19 + src/types/index.ts | 5 + src/utils/contracts/eas/sepoliaChain.json | 1051 +++++++++++++++++ 17 files changed, 1542 insertions(+), 143 deletions(-) create mode 100644 src/pages/Callback/Callback.tsx create mode 100644 src/pages/Callback/index.ts create mode 100644 src/services/api/auth/index.ts rename src/{ => services}/api/index.ts (76%) create mode 100644 src/services/api/linking/index.ts create mode 100644 src/services/api/linking/query.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/contracts/eas/sepoliaChain.json diff --git a/.gitignore b/.gitignore index 5f9b8b2..9dde2e1 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ dist-ssr coverage -.env \ No newline at end of file +.env + +/src/contracts/* \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 172f27a..2042c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,10 @@ "@mui/material": "^5.16.0", "@rainbow-me/rainbowkit": "^2.1.3", "@react-icons/all-files": "^4.1.0", - "@tanstack/react-query": "^5.51.16", + "@tanstack/react-query": "^5.51.21", "@tanstack/react-query-devtools": "^5.50.1", "axios": "^1.7.2", + "jwt-decode": "^4.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.52.1", @@ -7760,9 +7761,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.51.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.16.tgz", - "integrity": "sha512-zfV+WAtBGm1dUIbL0w/x8qTqVLKU1/Bo1p19J9LF02MmIc4FxzMImMXhFzYJQl5Hx8Wit6RiQ4tB/DvN8y9zaQ==", + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.51.21.tgz", + "integrity": "sha512-POQxm42IUp6n89kKWF4IZi18v3fxQWFRolvBA6phNVmA8psdfB1MvDnGacCJdS+EOX12w/CyHM62z//rHmYmvw==", "license": "MIT", "funding": { "type": "github", @@ -7779,12 +7780,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.51.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.16.tgz", - "integrity": "sha512-NZnpJ30zkwaA2ZPhxJLs/qoMbd0yNAj6yyb3JTADJx9HjSdtvnNzOY1bDa3bU1B9CZTBBb7W9E1PpWlNXdgESg==", + "version": "5.51.21", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.51.21.tgz", + "integrity": "sha512-Q/V81x3sAYgCsxjwOkfLXfrmoG+FmDhLeHH5okC/Bp8Aaw2c33lbEo/mMcMnkxUPVtB2FLpzHT0tq3c+OlZEbw==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.51.16" + "@tanstack/query-core": "5.51.21" }, "funding": { "type": "github", @@ -15078,6 +15079,15 @@ "node": ">=4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keccak": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", diff --git a/package.json b/package.json index 66e4cf7..32029ef 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,10 @@ "@mui/material": "^5.16.0", "@rainbow-me/rainbowkit": "^2.1.3", "@react-icons/all-files": "^4.1.0", - "@tanstack/react-query": "^5.51.16", + "@tanstack/react-query": "^5.51.21", "@tanstack/react-query-devtools": "^5.50.1", "axios": "^1.7.2", + "jwt-decode": "^4.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.52.1", diff --git a/src/App.tsx b/src/App.tsx index e5b7f43..3878b70 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,33 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import './App.css'; import '@rainbow-me/rainbowkit/styles.css'; -import { RouterProvider } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ThemeProvider } from '@mui/material/styles'; import CssBaseline from '@mui/material/CssBaseline'; +import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom'; +import { WagmiProvider } from 'wagmi'; import { + AuthenticationStatus, + createAuthenticationAdapter, getDefaultConfig, - RainbowKitProvider, RainbowKitAuthenticationProvider, - createAuthenticationAdapter, - AuthenticationStatus, + RainbowKitProvider, } from '@rainbow-me/rainbowkit'; -import { WagmiProvider } from 'wagmi'; -import { sepolia } from 'viem/chains'; +import { sepolia } from 'wagmi/chains'; import { getAddress } from 'viem'; import { createSiweMessage } from 'viem/siwe'; +import Login from './pages/Auth/Login'; import theme from './libs/theme'; -import { router } from './router'; -import { api } from './api'; -import { AuthProvider, useAuth } from './context/authContext'; +import { api } from './services/api'; + +import DefaultLayout from './layouts/DefaultLayout'; + +import Dashboard from './pages/Dashboard'; +import Identifiers from './pages/Identifiers'; +import Permissions from './pages/Permissions'; +import Attestation from './pages/Identifiers/Attestation'; +import Callback from './pages/Callback'; +import ProtectedRoute from './ProtectedRoute'; const queryClient = new QueryClient({ defaultOptions: { @@ -31,8 +39,14 @@ const queryClient = new QueryClient({ }, }); -const AuthenticationWrapper: React.FC = () => { - const { setAuthInfo, signOut } = useAuth(); +const config = getDefaultConfig({ + appName: 'RainbowKit demo', + projectId: '1cf030f3b91e339bc4e6ecf71a694a88', + chains: [sepolia], + ssr: false, +}); + +const App: React.FC = () => { const [authStatus, setAuthStatus] = useState('unauthenticated'); @@ -65,51 +79,81 @@ const AuthenticationWrapper: React.FC = () => { } if (data?.jwt) { + localStorage.setItem('OCI_TOKEN', data.jwt); setAuthStatus('authenticated'); - setAuthInfo(data.jwt); - window.location.replace('/'); - } else { - setAuthStatus('unauthenticated'); + return true; } - return data; + return false; }, signOut: async () => { - setAuthStatus('unauthenticated'); - signOut(); + localStorage.removeItem('OCI_TOKEN'); }, }); - const config = getDefaultConfig({ - appName: 'RainbowKit demo', - projectId: '1cf030f3b91e339bc4e6ecf71a694a88', - chains: [sepolia], - }); + useEffect(() => { + const checkStoredToken = () => { + const OCI_TOKEN = localStorage.getItem('OCI_TOKEN'); + if (OCI_TOKEN) { + setAuthStatus('authenticated'); + } else { + setAuthStatus('unauthenticated'); + } + }; - return ( - - - - - - - - ); -}; + checkStoredToken(); + }, []); + + useEffect(() => { + console.log('authStatus', authStatus); + }, [authStatus]); -const App: React.FC = () => { return ( - - - - - - - - + + + + + + + + + + ) : ( + + ) + } + /> + + + + } + > + } /> + } /> + } + /> + } /> + + } /> + Not found} /> + + + + + + + ); }; diff --git a/src/ProtectedRoute.tsx b/src/ProtectedRoute.tsx index 69173e4..732e684 100644 --- a/src/ProtectedRoute.tsx +++ b/src/ProtectedRoute.tsx @@ -1,23 +1,27 @@ -import { Navigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; +import { Navigate } from 'react-router-dom'; import CircularProgress from '@mui/material/CircularProgress'; import Backdrop from '@mui/material/Backdrop'; -import { useAuth } from './context/authContext'; -const ProtectedRoute = ({ children }: { children: JSX.Element }) => { - const { isAuthenticated } = useAuth(); - const [loading, setLoading] = useState(true); +interface ProtectedRouteProps { + children: JSX.Element; +} + +const ProtectedRoute = ({ children }: ProtectedRouteProps) => { + const [loading, setLoading] = useState(true); + const [isAuthenticated, setIsAuthenticated] = useState(false); useEffect(() => { const checkAuthStatus = async () => { - setLoading(true); - setTimeout(() => { - setLoading(false); - }, 1000); + const token = localStorage.getItem('OCI_TOKEN'); + if (token) { + setIsAuthenticated(true); + } + setLoading(false); }; checkAuthStatus(); - }, [isAuthenticated]); + }, []); if (loading) { return ( diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 47f0f0f..d5bfc9b 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -9,3 +9,7 @@ export interface MenuItem { }; children?: MenuItem[]; } + +export interface PlatformAuthenticationParams { + platformType: 'DISCORD' | 'GOOGLE'; +} diff --git a/src/pages/Callback/Callback.tsx b/src/pages/Callback/Callback.tsx new file mode 100644 index 0000000..a0e1f20 --- /dev/null +++ b/src/pages/Callback/Callback.tsx @@ -0,0 +1,80 @@ +import { useEffect } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { jwtDecode } from 'jwt-decode'; +import { Backdrop, CircularProgress } from '@mui/material'; + +interface DecodedJwt { + exp: number; + iat: number; + provider: string; + sub: string; +} + +const useQueryParams = () => { + const { search } = useLocation(); + return new URLSearchParams(search); +}; + +const isJwt = (token: string): boolean => { + const jwtRegex = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/; + return jwtRegex.test(token); +}; + +const getStoredTokens = (): Array<{ + token: string; + exp: number; + provider: string; +}> => { + const storedTokens = localStorage.getItem('OCI_PROVIDER_TOKENS'); + return storedTokens ? JSON.parse(storedTokens) : []; +}; + +const storeTokens = ( + tokens: Array<{ token: string; exp: number; provider: string }> +) => { + localStorage.setItem('OCI_PROVIDER_TOKENS', JSON.stringify(tokens)); +}; + +export function Callback() { + const queryParams = useQueryParams(); + const jwt = queryParams.get('jwt'); + const navigate = useNavigate(); + + useEffect(() => { + if (jwt && isJwt(jwt)) { + try { + const decodedJwt: DecodedJwt = jwtDecode(jwt); + const { provider, exp } = decodedJwt; + + // Retrieve existing tokens from localStorage + const storedTokens = getStoredTokens(); + + // Remove any existing token for the current provider + const updatedTokens = storedTokens.filter( + (token) => token.provider !== provider + ); + + // Add the new token + updatedTokens.push({ token: jwt, exp, provider }); + + // Store updated tokens back to localStorage + storeTokens(updatedTokens); + + // Redirect to the current JWT provider route + navigate(`/identifiers/${provider}/attestation?jwt=${jwt}`); + } catch (error) { + console.error('Invalid JWT:', error); + } + } else { + navigate('/identifiers'); + } + }, [jwt, navigate]); + + return ( + + + + ); +} + +export default Callback; diff --git a/src/pages/Callback/index.ts b/src/pages/Callback/index.ts new file mode 100644 index 0000000..4ab3846 --- /dev/null +++ b/src/pages/Callback/index.ts @@ -0,0 +1,3 @@ +import { Callback } from './Callback'; + +export default Callback; diff --git a/src/pages/Identifiers/Attestation/Attestation.tsx b/src/pages/Identifiers/Attestation/Attestation.tsx index c73416e..13bd1ca 100644 --- a/src/pages/Identifiers/Attestation/Attestation.tsx +++ b/src/pages/Identifiers/Attestation/Attestation.tsx @@ -1,69 +1,197 @@ -import { useState } from 'react'; +/* eslint-disable @typescript-eslint/no-shadow */ +import { useState, useEffect } from 'react'; import Paper from '@mui/material/Paper'; -import StepperComponent from '../../../components/shared/CustomStepper'; import Typography from '@mui/material/Typography'; import Button from '@mui/material/Button'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { jwtDecode } from 'jwt-decode'; +import { Address } from 'viem'; +import { useWriteContract, useAccount } from 'wagmi'; +import StepperComponent from '../../../components/shared/CustomStepper'; +import { platformAuthentication } from '../../../services/api/auth'; +import { useLinkIdentifierMutation } from '../../../services/api/linking/query'; +import sepoliaChain from '../../../utils/contracts/eas/sepoliaChain.json'; const steps = [{ label: 'Auth' }, { label: 'Attest' }, { label: 'Transact' }]; +type Provider = 'DISCORD' | 'GOOGLE'; +type Token = { token: string; exp: number; provider: Provider }; +type DecodedToken = { provider: Provider; iat: number; exp: number }; + export function Attestation() { - const [activeStep, setActiveStep] = useState(0); - - const handleNext = () => { - setActiveStep((prevActiveStep) => Math.min(prevActiveStep + 1, steps.length - 1)); - }; - - return ( - - -
- {activeStep === 0 && ( -
- - Let’s get started! - - - Please sign in with Discord. - - -
- )} - {activeStep === 1 && ( -
- - Generate an attestation. - - - An attestation is a proof that links your Discord account to your wallet address. - - -
- )} - {activeStep === 2 && ( -
- - Sign Transaction. - - - Signing the transaction will put your attestation on-chain. - - - - This will cost a small amount of gas. - -
- )} -
-
+ const { isConnected, address } = useAccount(); + const { writeContract, error } = useWriteContract(); + + useEffect(() => { + if (!isConnected) { + console.error('Not connected'); + } + }, [isConnected, address]); + + const { provider } = useParams<{ provider: 'DISCORD' | 'GOOGLE' }>(); + const location = useLocation(); + const navigate = useNavigate(); + const { mutate: mutateIdentifier, data: linkingIdentifier } = + useLinkIdentifierMutation(); + const [activeStep, setActiveStep] = useState(0); + const [linkingIdentifierRequest, setLinkingIdentifierRequest] = useState({}); + + const handleNext = () => { + setActiveStep((prevActiveStep) => + Math.min(prevActiveStep + 1, steps.length - 1) + ); + }; + + useEffect(() => { + if (linkingIdentifier) { + setLinkingIdentifierRequest(linkingIdentifier.data); + handleNext(); + } + }, [linkingIdentifier]); + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const jwtToken = searchParams.get('jwt'); + + if (jwtToken) { + try { + const decoded: DecodedToken = jwtDecode(jwtToken); + const { provider: jwtProvider } = decoded; + + const existingTokens: Token[] = JSON.parse( + localStorage.getItem('OCI_PROVIDER_TOKENS') || '[]' + ); + const updatedTokens = existingTokens.filter( + (token) => token.provider !== jwtProvider + ); + + updatedTokens.push({ + token: jwtToken, + exp: decoded.exp, + provider: jwtProvider, + }); + localStorage.setItem( + 'OCI_PROVIDER_TOKENS', + JSON.stringify(updatedTokens) + ); + + navigate(location.pathname, { replace: true }); + + setActiveStep(1); + } catch (error) { + console.error('Invalid JWT token:', error); + } + } + }, [location.search, location.pathname, navigate]); + + const handleAuthorize = () => { + if (!provider) return; + platformAuthentication({ platformType: provider }); + }; + + const getTokenForProvider = (jwtProvider: string) => { + const tokens = + JSON.parse(localStorage.getItem('OCI_PROVIDER_TOKENS') || '') || []; + const tokenObject = tokens.find( + (token: { provider: string }) => + token.provider.toLowerCase() === jwtProvider.toLowerCase() ); + return tokenObject ? tokenObject.token : null; + }; + + const handleAttest = () => { + writeContract({ + abi: sepoliaChain.easContractAbi, + address: sepoliaChain.easContractAddress as Address, + functionName: 'attestByDelegation', + args: [linkingIdentifierRequest], + }); + + console.log({ error }); + }; + + const handleLinkIdentifier = async () => { + const siweJwt = localStorage.getItem('OCI_TOKEN'); + if (!siweJwt || !provider) return; + const anyJwt = getTokenForProvider(provider); + mutateIdentifier({ + siweJwt, + anyJwt, + }); + }; + + return ( + + +
+ {activeStep === 0 && ( +
+ + Let’s get started! + + + Please sign in with {provider}. + + +
+ )} + {activeStep === 1 && ( +
+ + Generate an attestation. + + + An attestation is a proof that links your {provider} account to + your wallet address. + + +
+ )} + {activeStep === 2 && ( +
+ + Sign Transaction. + + + Signing the transaction will put your attestation on-chain. + + + + This will cost a small amount of gas. + +
+ )} +
+
+ ); } export default Attestation; diff --git a/src/pages/Identifiers/Identifiers.tsx b/src/pages/Identifiers/Identifiers.tsx index 15870c0..081271c 100644 --- a/src/pages/Identifiers/Identifiers.tsx +++ b/src/pages/Identifiers/Identifiers.tsx @@ -1,22 +1,36 @@ -import { List, ListItem, ListItemText, ListItemSecondaryAction, Button, Typography, Divider, Paper, Box, Avatar } from '@mui/material'; +import { + List, + ListItem, + ListItemText, + ListItemSecondaryAction, + Button, + Typography, + Divider, + Paper, + Box, + Avatar, +} from '@mui/material'; import VerifiedIcon from '@mui/icons-material/Verified'; -import { FaDiscord, FaTelegram, FaGoogle } from 'react-icons/fa'; +import { FaDiscord, FaGoogle } from 'react-icons/fa'; +import { useNavigate } from 'react-router-dom'; const identifiers = [ - { name: 'Discord', icon: FaDiscord, verified: true, color: 'text-blue-500' }, - { name: 'Telegram', icon: FaTelegram, verified: false, color: 'text-blue-400' }, + { name: 'Discord', icon: FaDiscord, verified: false, color: 'text-blue-500' }, { name: 'Google', icon: FaGoogle, verified: false, color: 'text-red-500' }, ]; -const handleRevoke = (identifier: string) => { - console.log(`Revoke attestation for ${identifier}`); -}; +export function Identifiers() { + const navigate = useNavigate(); -const handleConnect = (identifier: string) => { - console.log(`Connect identifier for ${identifier}`); -}; + const handleRevoke = (identifier: string) => { + console.log(`Revoke attestation for ${identifier}`); + }; + + const handleConnect = (identifier: string) => { + console.log(`Connect identifier for ${identifier}`); + navigate(`/identifiers/${identifier.toLowerCase()}/attestation`); + }; -export function Identifiers() { return (
@@ -34,15 +48,20 @@ export function Identifiers() { {identifiers.map((identifier, index) => ( - + - + - {identifier.verified && } + {identifier.verified && ( + + )} {identifier.name}
} diff --git a/src/router/index.tsx b/src/router/index.tsx index 491d1f6..c97f4b4 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -5,6 +5,7 @@ import Dashboard from '../pages/Dashboard'; import Identifiers from '../pages/Identifiers'; import Permissions from '../pages/Permissions'; import Attestation from '../pages/Identifiers/Attestation'; +import Callback from '../pages/Callback'; import DefaultLayout from '../layouts/DefaultLayout'; import ProtectedRoute from '../ProtectedRoute'; @@ -36,7 +37,7 @@ export const router = createBrowserRouter([ ), }, { - path: '/attestation', + path: 'identifiers/:provider/attestation', element: ( @@ -53,6 +54,10 @@ export const router = createBrowserRouter([ }, ], }, + { + path: '/callback', + element: , + }, { path: '*', element:
Not found
, diff --git a/src/services/api/auth/index.ts b/src/services/api/auth/index.ts new file mode 100644 index 0000000..3b0fdab --- /dev/null +++ b/src/services/api/auth/index.ts @@ -0,0 +1,8 @@ +import { baseURL } from '..'; +import { PlatformAuthenticationParams } from '@/interfaces'; + +export const platformAuthentication = async ({ + platformType, +}: PlatformAuthenticationParams) => { + window.location.replace(`${baseURL}auth/${platformType}/authenticate`); +}; diff --git a/src/api/index.ts b/src/services/api/index.ts similarity index 76% rename from src/api/index.ts rename to src/services/api/index.ts index 7cbd484..8147533 100644 --- a/src/api/index.ts +++ b/src/services/api/index.ts @@ -1,6 +1,6 @@ import axios from 'axios'; -const baseURL = import.meta.env.VITE_API_BASE_URL; +export const baseURL = import.meta.env.VITE_API_BASE_URL; if (!baseURL) { throw new Error( @@ -8,14 +8,14 @@ if (!baseURL) { ); } -export const api = axios.create({ +const apiInstance = axios.create({ baseURL, headers: { 'Content-Type': 'application/json', }, }); -api.interceptors.request.use( +apiInstance.interceptors.request.use( // eslint-disable-next-line @typescript-eslint/no-explicit-any (config: any) => { const token = localStorage.getItem('OCI_TOKEN'); @@ -35,4 +35,5 @@ api.interceptors.request.use( } ); -export default api; +export default apiInstance; +export { apiInstance as api }; diff --git a/src/services/api/linking/index.ts b/src/services/api/linking/index.ts new file mode 100644 index 0000000..4f1bfb6 --- /dev/null +++ b/src/services/api/linking/index.ts @@ -0,0 +1,15 @@ +import { api } from '..'; + +export interface LinkIdentifierParams { + siweJwt: string; + anyJwt: string; + chainId?: number; +} + +export const linkIdentifier = async ({ + siweJwt, + anyJwt, + chainId = 11155111, +}: LinkIdentifierParams) => { + return api.post('/linking/link-identities', { siweJwt, anyJwt, chainId }); +}; diff --git a/src/services/api/linking/query.ts b/src/services/api/linking/query.ts new file mode 100644 index 0000000..f6c1c42 --- /dev/null +++ b/src/services/api/linking/query.ts @@ -0,0 +1,19 @@ +import { useMutation } from '@tanstack/react-query'; +import { linkIdentifier, LinkIdentifierParams } from '.'; + +export const useLinkIdentifierMutation = () => { + return useMutation({ + mutationFn: async ({ + siweJwt, + anyJwt, + chainId = 11155111, + }: LinkIdentifierParams) => { + return linkIdentifier({ + siweJwt, + anyJwt, + chainId, + }); + }, + mutationKey: ['linkIdentifier'], + }); +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..bb3191f --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,5 @@ +export interface ApiResponse { + data: T; + success: boolean; + error?: string; +} diff --git a/src/utils/contracts/eas/sepoliaChain.json b/src/utils/contracts/eas/sepoliaChain.json new file mode 100644 index 0000000..6ec0c4e --- /dev/null +++ b/src/utils/contracts/eas/sepoliaChain.json @@ -0,0 +1,1051 @@ +{ + "chainId": 11155111, + "easSchemaUUID": "0x85e90e3e16d319578888790af3284fea8bca549305071531e7478e3e0b5e7d6d", + "easContractAddress": "0xC2679fBD37d54388Ce493F1DB75320D236e1815e", + "easContractAbi": [ + { + "inputs": [ + { + "internalType": "contract ISchemaRegistry", + "name": "registry", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AccessDenied", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyRevoked", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyRevokedOffchain", + "type": "error" + }, + { + "inputs": [], + "name": "AlreadyTimestamped", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientValue", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAttestation", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAttestations", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidExpirationTime", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidOffset", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRegistry", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRevocation", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRevocations", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSchema", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidVerifier", + "type": "error" + }, + { + "inputs": [], + "name": "Irrevocable", + "type": "error" + }, + { + "inputs": [], + "name": "NotFound", + "type": "error" + }, + { + "inputs": [], + "name": "NotPayable", + "type": "error" + }, + { + "inputs": [], + "name": "WrongSchema", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + } + ], + "name": "Attested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + } + ], + "name": "Revoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "revoker", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + } + ], + "name": "RevokedOffchain", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + } + ], + "name": "Timestamped", + "type": "event" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct AttestationRequestData", + "name": "data", + "type": "tuple" + } + ], + "internalType": "struct AttestationRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "attest", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct AttestationRequestData", + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct EIP712Signature", + "name": "signature", + "type": "tuple" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + } + ], + "internalType": "struct DelegatedAttestationRequest", + "name": "delegatedRequest", + "type": "tuple" + } + ], + "name": "attestByDelegation", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getAttestTypeHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + } + ], + "name": "getAttestation", + "outputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "time", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "uint64", + "name": "revocationTime", + "type": "uint64" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Attestation", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "getNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "revoker", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "getRevokeOffchain", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getRevokeTypeHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getSchemaRegistry", + "outputs": [ + { + "internalType": "contract ISchemaRegistry", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "getTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + } + ], + "name": "isAttestationValid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct AttestationRequestData[]", + "name": "data", + "type": "tuple[]" + } + ], + "internalType": "struct MultiAttestationRequest[]", + "name": "multiRequests", + "type": "tuple[]" + } + ], + "name": "multiAttest", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expirationTime", + "type": "uint64" + }, + { + "internalType": "bool", + "name": "revocable", + "type": "bool" + }, + { + "internalType": "bytes32", + "name": "refUID", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct AttestationRequestData[]", + "name": "data", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct EIP712Signature[]", + "name": "signatures", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "attester", + "type": "address" + } + ], + "internalType": "struct MultiDelegatedAttestationRequest[]", + "name": "multiDelegatedRequests", + "type": "tuple[]" + } + ], + "name": "multiAttestByDelegation", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct RevocationRequestData[]", + "name": "data", + "type": "tuple[]" + } + ], + "internalType": "struct MultiRevocationRequest[]", + "name": "multiRequests", + "type": "tuple[]" + } + ], + "name": "multiRevoke", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct RevocationRequestData[]", + "name": "data", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct EIP712Signature[]", + "name": "signatures", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "revoker", + "type": "address" + } + ], + "internalType": "struct MultiDelegatedRevocationRequest[]", + "name": "multiDelegatedRequests", + "type": "tuple[]" + } + ], + "name": "multiRevokeByDelegation", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "data", + "type": "bytes32[]" + } + ], + "name": "multiRevokeOffchain", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "data", + "type": "bytes32[]" + } + ], + "name": "multiTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct RevocationRequestData", + "name": "data", + "type": "tuple" + } + ], + "internalType": "struct RevocationRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "revoke", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "schema", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "internalType": "struct RevocationRequestData", + "name": "data", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "internalType": "struct EIP712Signature", + "name": "signature", + "type": "tuple" + }, + { + "internalType": "address", + "name": "revoker", + "type": "address" + } + ], + "internalType": "struct DelegatedRevocationRequest", + "name": "delegatedRequest", + "type": "tuple" + } + ], + "name": "revokeByDelegation", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "revokeOffchain", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "data", + "type": "bytes32" + } + ], + "name": "timestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "permissionManagerContractAddress": "0x787aeDd9Fb3e16EeF5b00C0F35f105daD2A1aA15", + "permissionManagerContractFunctionName": "hasPermission", + "permissionManagerContractAbi": { + "inputs": [ + { + "internalType": "bytes32", + "name": "uid", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasPermission", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +}