diff --git a/packages/demos/email-recovery/src/App.css b/packages/demos/email-recovery/src/App.css
index b9d355df..617ee8d5 100644
--- a/packages/demos/email-recovery/src/App.css
+++ b/packages/demos/email-recovery/src/App.css
@@ -1,8 +1,5 @@
#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
+
}
.logo {
@@ -32,11 +29,3 @@
animation: logo-spin infinite 20s linear;
}
}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/packages/demos/email-recovery/src/App.tsx b/packages/demos/email-recovery/src/App.tsx
index c435090f..a03289ea 100644
--- a/packages/demos/email-recovery/src/App.tsx
+++ b/packages/demos/email-recovery/src/App.tsx
@@ -1,23 +1,56 @@
-import './App.css'
-import { ConfigureSafeModule } from './components/ConfigureSafeModule';
-import { PerformRecovery } from './components/PerformRecovery';
-import { AppContextProvider } from './context/AppContextProvider';
+import { createContext, useEffect, useState } from "react";
+import "./App.css";
+import ConnectWallets from "./components/ConnectWallets";
+import Navbar from "./components/Navbar";
+import RequestedRecoveries from "./components/RequestedRecoveries";
+import RequestGuardian from "./components/RequestGuardian";
+import SafeModuleRecovery from "./components/SafeModuleRecovery";
+import TriggerAccountRecovery from "./components/TriggerAccountRecovery";
+import { STEPS } from "./constants";
import { Web3Provider } from "./providers/Web3Provider";
import { ConnectKitButton } from "connectkit";
+import { useAccount } from "wagmi";
+import { AppContextProvider } from "./context/AppContextProvider";
+
+export const StepsContext = createContext(null);
function App() {
+ const [step, setStep] = useState(STEPS.CONNECT_WALLETS);
+
+ const renderBody = () => {
+ switch (step) {
+ case STEPS.CONNECT_WALLETS:
+ return ;
+ case STEPS.SAFE_MODULE_RECOVERY:
+ return ;
+ case STEPS.REQUEST_GUARDIAN:
+ return ;
+ case STEPS.REQUESTED_RECOVERIES:
+ return ;
+ case STEPS.TRIGGER_ACCOUNT_RECOVERY:
+ return ;
+ default:
+ return ;
+ }
+ };
+
return (
- <>
-
Safe Email Recovery Demo
-
-
-
-
-
-
-
- >
- )
+
+
+
+
+
+
Safe Email Recovery Demo
+ {renderBody()}
+
+ {" "}
+
+
+ );
}
-export default App
+export default App;
diff --git a/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.svg b/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.svg
new file mode 100644
index 00000000..435c03c1
--- /dev/null
+++ b/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.zip b/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.zip
new file mode 100644
index 00000000..c9465de6
Binary files /dev/null and b/packages/demos/email-recovery/src/assets/cancelRecoveryIcon.zip differ
diff --git a/packages/demos/email-recovery/src/assets/completeRecoveryIcon.svg b/packages/demos/email-recovery/src/assets/completeRecoveryIcon.svg
new file mode 100644
index 00000000..09845d56
--- /dev/null
+++ b/packages/demos/email-recovery/src/assets/completeRecoveryIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/demos/email-recovery/src/assets/completeRecoveryIcon.zip b/packages/demos/email-recovery/src/assets/completeRecoveryIcon.zip
new file mode 100644
index 00000000..edc57e48
Binary files /dev/null and b/packages/demos/email-recovery/src/assets/completeRecoveryIcon.zip differ
diff --git a/packages/demos/email-recovery/src/assets/infoIcon.svg b/packages/demos/email-recovery/src/assets/infoIcon.svg
new file mode 100644
index 00000000..55284b5b
--- /dev/null
+++ b/packages/demos/email-recovery/src/assets/infoIcon.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/demos/email-recovery/src/assets/recoveredIcon.svg b/packages/demos/email-recovery/src/assets/recoveredIcon.svg
new file mode 100644
index 00000000..9d213885
--- /dev/null
+++ b/packages/demos/email-recovery/src/assets/recoveredIcon.svg
@@ -0,0 +1,17 @@
+
diff --git a/packages/demos/email-recovery/src/assets/wallet.svg b/packages/demos/email-recovery/src/assets/wallet.svg
new file mode 100644
index 00000000..30a56249
--- /dev/null
+++ b/packages/demos/email-recovery/src/assets/wallet.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/demos/email-recovery/src/assets/wallet.zip b/packages/demos/email-recovery/src/assets/wallet.zip
new file mode 100644
index 00000000..8464606e
Binary files /dev/null and b/packages/demos/email-recovery/src/assets/wallet.zip differ
diff --git a/packages/demos/email-recovery/src/components/Button.tsx b/packages/demos/email-recovery/src/components/Button.tsx
index 67e9d9cd..6e89d188 100644
--- a/packages/demos/email-recovery/src/components/Button.tsx
+++ b/packages/demos/email-recovery/src/components/Button.tsx
@@ -1,11 +1,16 @@
-import React from 'react';
+import React from "react";
-export function Button({ children, ...buttonProps }: React.ComponentPropsWithoutRef<"button">) {
- return (
-
-
-
- )
+export function Button({
+ children,
+ ...buttonProps
+}: React.ComponentPropsWithoutRef<"button">) {
+ return (
+
+
+
+ );
}
diff --git a/packages/demos/email-recovery/src/components/ConnectWallets.tsx b/packages/demos/email-recovery/src/components/ConnectWallets.tsx
new file mode 100644
index 00000000..d65481ab
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/ConnectWallets.tsx
@@ -0,0 +1,44 @@
+import { Button } from "./Button";
+import walletIcon from "../assets/wallet.svg";
+import infoIcon from "../assets/infoIcon.svg";
+import { Web3Provider } from "../providers/Web3Provider";
+import { ConnectKitButton } from "connectkit";
+import { useAccount } from "wagmi";
+import { useContext } from "react";
+import { StepsContext } from "../App";
+import { STEPS } from "../constants";
+
+const ConnectWallets = () => {
+ const { address } = useAccount();
+ const stepsContext = useContext(StepsContext);
+
+ if (address) {
+ console.log(stepsContext);
+ stepsContext?.setStep(STEPS.SAFE_MODULE_RECOVERY);
+ }
+
+ return (
+
+
}>Connect Genosis Safe
+
+
+
+ Copy the link and import into your safe wallet
+
+
+ {({ isConnected, show, truncatedAddress, ensName }) => {
+ return (
+ }>
+ Connect Test Wallet
+
+ );
+ }}
+
+
+ Or, recover existing wallet instead ➔
+
+
+ );
+};
+
+export default ConnectWallets;
diff --git a/packages/demos/email-recovery/src/components/Navbar.tsx b/packages/demos/email-recovery/src/components/Navbar.tsx
new file mode 100644
index 00000000..cede0e57
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/Navbar.tsx
@@ -0,0 +1,13 @@
+import { Web3Provider } from "../providers/Web3Provider";
+import { ConnectKitButton } from "connectkit";
+import { Button } from "./Button";
+
+const Navbar = () => {
+ return (
+
+ );
+};
+
+export default Navbar;
diff --git a/packages/demos/email-recovery/src/components/RequestGuardian.tsx b/packages/demos/email-recovery/src/components/RequestGuardian.tsx
new file mode 100644
index 00000000..10c11d4a
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/RequestGuardian.tsx
@@ -0,0 +1,215 @@
+import { useCallback, useContext, useMemo, useState } from "react";
+import { ConnectKitButton } from "connectkit";
+import { Button } from "./Button";
+import { useAccount, useReadContract, useWriteContract } from "wagmi";
+import { abi as safeAbi } from "../abi/Safe.json";
+import { useAppContext } from "../context/AppContextHook";
+
+import { abi as recoveryPluginAbi } from "../abi/SafeZkEmailRecoveryPlugin.json";
+import { safeZkSafeZkEmailRecoveryPlugin } from "../../contracts.base-sepolia.json";
+import {
+ genAccountCode,
+ getRequestGuardianSubject,
+ templateIdx,
+} from "../utils/email";
+import { readContract } from "wagmi/actions";
+import { config } from "../providers/config";
+import { pad } from "viem";
+import { relayer } from "../services/relayer";
+import { StepsContext } from "../App";
+import { STEPS } from "../constants";
+
+const RequestGuardian = () => {
+ const { address } = useAccount();
+ const { writeContractAsync } = useWriteContract();
+
+ const { guardianEmail, setGuardianEmail, accountCode, setAccountCode } =
+ useAppContext();
+ const stepsContext = useContext(StepsContext);
+
+ const [recoveryDelay, setRecoveryDelay] = useState(0);
+
+ const isMobile = window.innerWidth < 768;
+
+ const { data: safeOwnersData } = useReadContract({
+ address,
+ abi: safeAbi,
+ functionName: "getOwners",
+ });
+ const firstSafeOwner = useMemo(() => {
+ const safeOwners = safeOwnersData as string[];
+ if (!safeOwners?.length) {
+ return;
+ }
+ return safeOwners[0];
+ }, [safeOwnersData]);
+
+ const configureRecoveryAndRequestGuardian = useCallback(async () => {
+ if (!address) {
+ throw new Error("unable to get account address");
+ }
+
+ if (!guardianEmail) {
+ throw new Error("guardian email not set");
+ }
+
+ if (!firstSafeOwner) {
+ throw new Error("safe owner not found");
+ }
+
+ const acctCode = await genAccountCode();
+ setAccountCode(accountCode);
+
+ const guardianSalt = await relayer.getAccountSalt(acctCode, guardianEmail);
+ const guardianAddr = await readContract(config, {
+ abi: recoveryPluginAbi,
+ address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
+ functionName: "computeEmailAuthAddress",
+ args: [guardianSalt],
+ });
+ // TODO Should this be something else?
+ const previousOwnerInLinkedList = pad("0x1", {
+ size: 20,
+ });
+
+ try {
+ await writeContractAsync({
+ abi: recoveryPluginAbi,
+ address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
+ functionName: "configureRecovery",
+ args: [
+ firstSafeOwner,
+ guardianAddr,
+ recoveryDelay,
+ previousOwnerInLinkedList,
+ ],
+ });
+ } catch (error) {
+ console.log(error);
+ }
+
+ console.debug("recovery configured");
+
+ const recoveryRouterAddr = (await readContract(config, {
+ abi: recoveryPluginAbi,
+ address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
+ functionName: "getRouterForSafe",
+ args: [address],
+ })) as string;
+
+ const subject = getRequestGuardianSubject(address);
+ const { requestId } = await relayer.acceptanceRequest(
+ recoveryRouterAddr,
+ guardianEmail,
+ acctCode,
+ templateIdx,
+ subject
+ );
+
+ let checkGuardianAcceptanceInterval = null
+
+ const checkGuardianAcceptance = async () => {
+ if (!requestId) {
+ throw new Error("missing guardian request id");
+ }
+
+ const resBody = await relayer.requestStatus(requestId);
+ console.debug("guardian req res body", resBody);
+
+ if(resBody?.is_success) {
+ stepsContext?.setStep(STEPS.REQUESTED_RECOVERIES);
+ checkGuardianAcceptanceInterval?.clearInterval()
+ }
+ }
+
+ checkGuardianAcceptanceInterval = setInterval(async () => {
+ const res = await checkGuardianAcceptance();
+ console.log(res)
+ }, 5000);
+
+ // TODO poll until guard req is complete or fails
+ }, [
+ address,
+ firstSafeOwner,
+ guardianEmail,
+ recoveryDelay,
+ accountCode,
+ setAccountCode,
+ writeContractAsync,
+ ]);
+
+ return (
+
+
+ Connected wallet:
+
+
+
+ Guardian Details:
+
+
+
+
Guardian's Email
+
setGuardianEmail(e.target.value)}
+ />
+
+
+ Recovery Delay
+ setRecoveryDelay(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+ );
+};
+
+export default RequestGuardian;
diff --git a/packages/demos/email-recovery/src/components/RequestedRecoveries.tsx b/packages/demos/email-recovery/src/components/RequestedRecoveries.tsx
new file mode 100644
index 00000000..42957c96
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/RequestedRecoveries.tsx
@@ -0,0 +1,266 @@
+import { useCallback, useContext, useState } from "react";
+import { Web3Provider } from "../providers/Web3Provider";
+import { ConnectKitButton } from "connectkit";
+import { Button } from "./Button";
+import cancelRecoveryIcon from "../assets/cancelRecoveryIcon.svg";
+import completeRecoveryIcon from "../assets/completeRecoveryIcon.svg";
+import recoveredIcon from "../assets/recoveredIcon.svg";
+import { useAppContext } from "../context/AppContextHook";
+import { useAccount, useReadContract } from "wagmi";
+
+import { relayer } from "../services/relayer";
+import { abi as recoveryPluginAbi } from "../abi/SafeZkEmailRecoveryPlugin.json";
+import { getRequestsRecoverySubject, templateIdx } from "../utils/email";
+import { safeZkSafeZkEmailRecoveryPlugin } from "../../contracts.base-sepolia.json";
+import { StepsContext } from "../App";
+import { STEPS } from "../constants";
+
+const BUTTON_STATES = {
+ TRIGGER_RECOVERY: "Trigger Recovery",
+ CANCEL_RECOVERY: "Cancel Recovery",
+ COMPLETE_RECOVERY: "Complete Recovery",
+ RECOVERY_COMPLETED: "Recovery Completed",
+};
+
+const RequestedRecoveries = () => {
+ const isMobile = window.innerWidth < 768;
+ const { address } = useAccount();
+ const { guardianEmail, setGuardianEmail } = useAppContext();
+ const stepsContext = useContext(StepsContext);
+
+ const [newOwner, setNewOwner] = useState();
+ const [buttonState, setButtonState] = useState(
+ BUTTON_STATES.TRIGGER_RECOVERY
+ );
+ const [loading, setLoading] = useState(false);
+ const [gurdianRequestId, setGuardianRequestId] = useState();
+
+ const { data: recoveryRouterAddr } = useReadContract({
+ abi: recoveryPluginAbi,
+ address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
+ functionName: "getRouterForSafe",
+ args: [address],
+ });
+
+ const requestRecovery = useCallback(async () => {
+ setLoading(true);
+ if (!address) {
+ throw new Error("unable to get account address");
+ }
+
+ if (!guardianEmail) {
+ throw new Error("guardian email not set");
+ }
+
+ if (!newOwner) {
+ throw new Error("new owner not set");
+ }
+
+ if (!recoveryRouterAddr) {
+ throw new Error("could not find recovery router for safe");
+ }
+
+ const subject = getRequestsRecoverySubject(address, newOwner);
+
+ const { requestId } = await relayer.recoveryRequest(
+ recoveryRouterAddr as string,
+ guardianEmail,
+ templateIdx,
+ subject
+ );
+
+ setGuardianRequestId(requestId);
+
+
+ let checkRequestRecoveryStatusInterval = null
+
+ const checkGuardianAcceptance = async () => {
+ if (!requestId) {
+ throw new Error("missing guardian request id");
+ }
+
+ const resBody = await relayer.requestStatus(requestId);
+ console.debug("guardian req res body", resBody);
+
+ if(resBody?.is_success) {
+ stepsContext?.setStep(STEPS.REQUESTED_RECOVERIES);
+ checkRequestRecoveryStatusInterval?.clearInterval()
+ }
+ }
+
+ checkRequestRecoveryStatusInterval = setInterval(async () => {
+ const res = await checkGuardianAcceptance();
+ console.log(res)
+ }, 5000);
+
+
+ setLoading(false);
+ setButtonState(BUTTON_STATES.COMPLETE_RECOVERY);
+ }, [recoveryRouterAddr, address, guardianEmail, newOwner]);
+
+ const completeRecovery = useCallback(async () => {
+ setLoading(true);
+ if (!recoveryRouterAddr) {
+ throw new Error("could not find recovery router for safe");
+ }
+
+ const res = relayer.completeRecovery(recoveryRouterAddr as string);
+
+ console.debug("complete recovery res", res);
+ setLoading(false);
+
+ setButtonState(BUTTON_STATES.RECOVERY_COMPLETED);
+ }, [recoveryRouterAddr]);
+
+ const checkGuardianAcceptance = useCallback(async () => {
+ if (!gurdianRequestId) {
+ throw new Error("missing guardian request id");
+ }
+
+ const resBody = await relayer.requestStatus(gurdianRequestId);
+ console.debug("guardian req res body", resBody);
+ }, [gurdianRequestId]);
+
+ const getButtonComponent = () => {
+ switch (buttonState) {
+ case BUTTON_STATES.TRIGGER_RECOVERY:
+ return (
+ }
+ >
+ Trigger Recovery
+
+ );
+ case BUTTON_STATES.CANCEL_RECOVERY:
+ return (
+ }>
+ Cancel Recovery
+
+ );
+ case BUTTON_STATES.COMPLETE_RECOVERY:
+ return (
+ }
+ >
+ Complete Recovery
+
+ );
+ case BUTTON_STATES.RECOVERY_COMPLETED:
+ return (
+
+ );
+ }
+ };
+
+ return (
+
+
+ Connected wallet:
+
+
+ {buttonState === BUTTON_STATES.RECOVERY_COMPLETED ? (
+
+
+ Recovered
+
+ ) : null}
+
+
+ {buttonState === BUTTON_STATES.RECOVERY_COMPLETED ? null : (
+
+ Requested Recoveries:
+
+
+
+
Guardian's Email
+
setGuardianEmail(e.target.value)}
+ />
+
+
+
Requested New Wallet Address
+
setNewOwner(e.target.value)}
+ />
+
+
+
+
+ )}
+
{getButtonComponent()}
+
+ );
+};
+
+export default RequestedRecoveries;
diff --git a/packages/demos/email-recovery/src/components/SafeModuleRecovery.tsx b/packages/demos/email-recovery/src/components/SafeModuleRecovery.tsx
new file mode 100644
index 00000000..9c0a63ca
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/SafeModuleRecovery.tsx
@@ -0,0 +1,60 @@
+import { ConnectKitButton } from "connectkit";
+import { Button } from "./Button";
+import { useAccount, useReadContract, useWriteContract } from "wagmi";
+import { safeZkSafeZkEmailRecoveryPlugin } from "../../contracts.base-sepolia.json";
+import { abi as safeAbi } from "../abi/Safe.json";
+import { useCallback, useContext, useState } from "react";
+import { StepsContext } from "../App";
+import { STEPS } from "../constants";
+
+const SafeModuleRecovery = () => {
+ const { address } = useAccount();
+ const { writeContractAsync } = useWriteContract();
+ const stepsContext = useContext(StepsContext);
+ const [loading, setLoading] = useState(false);
+
+ const { data: isModuleEnabled } = useReadContract({
+ address,
+ abi: safeAbi,
+ functionName: "isModuleEnabled",
+ args: [safeZkSafeZkEmailRecoveryPlugin],
+ });
+
+ console.log(isModuleEnabled);
+
+ if (isModuleEnabled) {
+ console.log("Module is enabled");
+ setLoading(false);
+ stepsContext?.setStep(STEPS.REQUEST_GUARDIAN);
+ }
+
+ const enableEmailRecoveryModule = useCallback(async () => {
+ setLoading(true);
+ if (!address) {
+ throw new Error("unable to get account address");
+ }
+
+ await writeContractAsync({
+ abi: safeAbi,
+ address,
+ functionName: "enableModule",
+ args: [safeZkSafeZkEmailRecoveryPlugin],
+ });
+
+ }, [address, writeContractAsync]);
+
+ return (
+
+
+ Connected wallet:
+
+ {!isModuleEnabled ? (
+
+ ) : null}
+
+ );
+};
+
+export default SafeModuleRecovery;
diff --git a/packages/demos/email-recovery/src/components/TriggerAccountRecovery.tsx b/packages/demos/email-recovery/src/components/TriggerAccountRecovery.tsx
new file mode 100644
index 00000000..e054760c
--- /dev/null
+++ b/packages/demos/email-recovery/src/components/TriggerAccountRecovery.tsx
@@ -0,0 +1,126 @@
+import { useState } from "react";
+import { Web3Provider } from "../providers/Web3Provider";
+import { ConnectKitButton } from "connectkit";
+import { Button } from "./Button";
+import cancelRecoveryIcon from "../assets/cancelRecoveryIcon.svg";
+import completeRecoveryIcon from "../assets/completeRecoveryIcon.svg";
+
+const BUTTON_STATES = {
+ CANCEL_RECOVERY: "Cancel Recovery",
+ COMPLETE_RECOVERY: "Complete Recovery",
+};
+
+const TriggerAccountRecovery = () => {
+ const isMobile = window.innerWidth < 768;
+
+ const [guardianEmail, setGuardianEmail] = useState("");
+ const [newWalletAddress, setNewWalletAddress] = useState("");
+ const [buttonState, setButtonState] = useState(BUTTON_STATES.CANCEL_RECOVERY);
+
+ return (
+
+
+
+ Connected wallet:
+
+
+
+ Triggered Account Recoveries:
+
+
+
+
Guardian's Email
+
setGuardianEmail(e.target.value)}
+ />
+
+
+
Previous Wallet Address
+
setGuardianEmail(e.target.value)}
+ />
+
+
+
New Wallet Address
+
setNewWalletAddress(e.target.value)}
+ />
+
+
+
+
+
+
+ ) : (
+
+ )
+ }
+ >
+ {buttonState === BUTTON_STATES.CANCEL_RECOVERY
+ ? "Cancel "
+ : "Complete"}
+ Recovery
+
+
+
+
+ );
+};
+
+export default TriggerAccountRecovery;
diff --git a/packages/demos/email-recovery/src/constants.ts b/packages/demos/email-recovery/src/constants.ts
new file mode 100644
index 00000000..e458dd63
--- /dev/null
+++ b/packages/demos/email-recovery/src/constants.ts
@@ -0,0 +1,7 @@
+export const STEPS = {
+ CONNECT_WALLETS: 0,
+ SAFE_MODULE_RECOVERY: 1,
+ REQUEST_GUARDIAN: 2,
+ REQUESTED_RECOVERIES: 3,
+ TRIGGER_ACCOUNT_RECOVERY: 4,
+};
\ No newline at end of file
diff --git a/packages/demos/email-recovery/src/index.css b/packages/demos/email-recovery/src/index.css
index 6119ad9a..31239db1 100644
--- a/packages/demos/email-recovery/src/index.css
+++ b/packages/demos/email-recovery/src/index.css
@@ -5,7 +5,7 @@
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
+ background-color: #0C111D;
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -18,21 +18,30 @@ a {
color: #646cff;
text-decoration: inherit;
}
+
a:hover {
color: #535bf2;
}
body {
margin: 0;
+}
+
+.app {
display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
+ padding: 0 2rem;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ height: 100vh;
}
h1 {
- font-size: 3.2em;
+ font-size: 2.25rem;
line-height: 1.1;
+ text-align: center;
+ font-weight: 600;
}
button {
@@ -40,18 +49,27 @@ button {
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
- font-weight: 500;
+ display: flex;
+ gap: 1rem;
+ font-weight: 600;
font-family: inherit;
- background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
+ border: none;
+ box-shadow: 0px 1px 2px 0px #1018280D;
+ background: linear-gradient(354.6deg, #0069E4 37.48%, #37C3FF 107.66%);
+ border-image-source: linear-gradient(144.35deg, #0069E4 33.65%, #37C3FF 93.17%);
+ padding: 22px;
}
+
+
button:hover {
border-color: #646cff;
}
+
button:focus,
button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
+ /* outline: 4px auto -webkit-focus-ring-color; */
}
@media (prefers-color-scheme: light) {
@@ -59,10 +77,77 @@ button:focus-visible {
color: #213547;
background-color: #ffffff;
}
+
a:hover {
color: #747bff;
}
+
button {
background-color: #f9f9f9;
}
}
+
+.navbar {
+ position: absolute;
+ top: 1.25rem;
+ right: 1.25rem;
+ width: 100vw;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+}
+
+.connect-wallets-container {
+ display: flex;
+ gap: 1rem;
+ margin-top: 1rem;
+ flex-direction: column;
+ width: fit-content;
+ align-items: center;
+}
+
+input {
+ background: var(--Colors-Background-bg-tertiary, #1F242F);
+ border: 1px solid var(--Colors-Border-border-primary, #333741);
+ box-shadow: 0px 1px 2px 0px #1018280D;
+ border-radius: 4px;
+ padding: 8px 12px;
+ color: #85888E;
+
+}
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ align-items: center;
+ background: var(--Colors-Background-bg-secondary, #161B26);
+
+ border: 1px solid var(--Colors-Border-border-primary, #333741);
+ padding: 20px 24px 20px 24px;
+ gap: 20px;
+ border-radius: 12px;
+ border: 1px 0px 0px 0px;
+ opacity: 0px;
+ color: #94969C;
+ font-weight: 500;
+}
+
+.loader {
+ border: 2px solid #f3f3f3;
+ border-top: 2px solid #3498db;
+ border-radius: 50%;
+ width: 12px;
+ height: 12px;
+ animation: spin 2s linear infinite;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
\ No newline at end of file