From 672177dc1a5d6eff8db1e8ad92399cbbf171f88d Mon Sep 17 00:00:00 2001 From: jacque006 Date: Mon, 8 Apr 2024 01:08:06 -0400 Subject: [PATCH] Finish hooking in write call for rest of recovery process. --- .github/workflows/email-recovery-demo.yml | 2 +- packages/demos/email-recovery/src/App.tsx | 9 +- .../src/components/ConfigureSafeModule.tsx | 48 +++-- .../src/components/PerformRecovery.tsx | 189 +++++------------- .../email-recovery/src/context/AppContext.tsx | 16 ++ .../src/context/AppContextHook.ts | 4 + .../src/context/AppContextProvider.tsx | 23 +++ .../email-recovery/src/services/relayer.ts | 17 +- .../demos/email-recovery/src/utils/email.ts | 2 +- 9 files changed, 136 insertions(+), 174 deletions(-) create mode 100644 packages/demos/email-recovery/src/context/AppContext.tsx create mode 100644 packages/demos/email-recovery/src/context/AppContextHook.ts create mode 100644 packages/demos/email-recovery/src/context/AppContextProvider.tsx diff --git a/.github/workflows/email-recovery-demo.yml b/.github/workflows/email-recovery-demo.yml index b227ae7..a9d52a9 100644 --- a/.github/workflows/email-recovery-demo.yml +++ b/.github/workflows/email-recovery-demo.yml @@ -46,7 +46,7 @@ jobs: run: yarn install --frozen-lockfile - name: Build env: - SUPER_SECRET: ${{ secrets.VITE_WALLET_CONNECT_PROJECT_ID }} + VITE_WALLET_CONNECT_PROJECT_ID: ${{ secrets.VITE_WALLET_CONNECT_PROJECT_ID }} run: VITE_WALLET_CONNECT_PROJECT_ID=${VITE_WALLET_CONNECT_PROJECT_ID} yarn build - name: Setup Pages diff --git a/packages/demos/email-recovery/src/App.tsx b/packages/demos/email-recovery/src/App.tsx index a2ed345..c435090 100644 --- a/packages/demos/email-recovery/src/App.tsx +++ b/packages/demos/email-recovery/src/App.tsx @@ -1,6 +1,7 @@ import './App.css' import { ConfigureSafeModule } from './components/ConfigureSafeModule'; import { PerformRecovery } from './components/PerformRecovery'; +import { AppContextProvider } from './context/AppContextProvider'; import { Web3Provider } from "./providers/Web3Provider"; import { ConnectKitButton } from "connectkit"; @@ -9,9 +10,11 @@ function App() { <>

Safe Email Recovery Demo

- - - + + + + + ) diff --git a/packages/demos/email-recovery/src/components/ConfigureSafeModule.tsx b/packages/demos/email-recovery/src/components/ConfigureSafeModule.tsx index f8893fa..e2e17d1 100644 --- a/packages/demos/email-recovery/src/components/ConfigureSafeModule.tsx +++ b/packages/demos/email-recovery/src/components/ConfigureSafeModule.tsx @@ -9,13 +9,18 @@ import { readContract } from 'wagmi/actions' import { config } from '../providers/config' import { pad } from 'viem' import { relayer } from '../services/relayer' +import { useAppContext } from '../context/AppContextHook' export function ConfigureSafeModule() { const { address } = useAccount() const { writeContractAsync } = useWriteContract() - const [recoveryConfigured, setRecoveryConfigured] = useState(false) - const [guardianEmail, setGuardianEmail] = useState() + const { + guardianEmail, + setGuardianEmail, + accountCode, + setAccountCode + } = useAppContext() // TODO 0 sets recovery to default of 2 weeks, likely want a warning here // Also, better time duration setting component const [recoveryDelay, setRecoveryDelay] = useState(0) @@ -40,6 +45,15 @@ export function ConfigureSafeModule() { return safeOwners[0]; }, [safeOwnersData]); + // 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 enableEmailRecoveryModule = useCallback(async () => { if (!address) { throw new Error('unable to get account address'); @@ -66,8 +80,10 @@ export function ConfigureSafeModule() { throw new Error('safe owner not found') } - const accountCode = await genAccountCode(); - const guardianSalt = await relayer.getAccountSalt(accountCode, guardianEmail); + 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}`, @@ -93,7 +109,7 @@ export function ConfigureSafeModule() { console.debug('recovery configured'); - const recoveryRelayerAddr = await readContract(config, { + const recoveryRouterAddr = await readContract(config, { abi: recoveryPluginAbi, address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`, functionName: 'getRouterForSafe', @@ -102,33 +118,29 @@ export function ConfigureSafeModule() { const subject = getRequestGuardianSubject(address); const { requestId } = await relayer.acceptanceRequest( - recoveryRelayerAddr, + recoveryRouterAddr, guardianEmail, - accountCode, + acctCode, templateIdx, subject, ); console.debug('req guard req id', requestId) - - setRecoveryConfigured(true); + // TODO poll until guard req is complete or fails }, [ address, firstSafeOwner, guardianEmail, recoveryDelay, + accountCode, + setAccountCode, writeContractAsync ]) - const recoveryCfgEnabled = useMemo( - () => !isModuleEnabled || recoveryConfigured, - [isModuleEnabled, recoveryConfigured] - ); - return ( <> { - isModuleEnabled ? + isModuleEnabled ?
Recovery Module Enabled
: diff --git a/packages/demos/email-recovery/src/components/PerformRecovery.tsx b/packages/demos/email-recovery/src/components/PerformRecovery.tsx index 0142a52..a8e1c77 100644 --- a/packages/demos/email-recovery/src/components/PerformRecovery.tsx +++ b/packages/demos/email-recovery/src/components/PerformRecovery.tsx @@ -1,121 +1,40 @@ -import { waitForTransactionReceipt } from '@wagmi/core' import { useState, useCallback } from 'react' import { Button } from './Button' import { relayer } from '../services/relayer' -import { useConfig, useReadContract, useWalletClient } from 'wagmi' -import { abi as proxyAbi, bytecode as proxyBytecode } from '../abi/ERC1967Proxy.json' -import { abi as simpleWalletAbi } from '../abi/SimpleWallet.json' +import { abi as recoveryPluginAbi } from '../abi/SafeZkEmailRecoveryPlugin.json' +import { useReadContract, useAccount } from 'wagmi' import { - verifier, - dkimRegistry, - emailAuthImpl, - simpleWalletImpl -} from '../../contracts.base-sepolia.json' -import { ethers } from 'ethers' -import { - genAccountCode, - getRequestGuardianSubject, getRequestsRecoverySubject, templateIdx } from '../utils/email' +import { safeZkSafeZkEmailRecoveryPlugin } from '../../contracts.base-sepolia.json' +import { useAppContext } from '../context/AppContextHook' -// TODO Pull from lib -type HexStr = `0x${string}`; - -const storageKeys = { - simpleWalletAddress: 'simpleWalletAddress', - guardianEmail: 'guardianEmail', - accountCode: 'accountCode', -} - -// TODO Switch back to Safe over SimpleWallet export function PerformRecovery() { - const cfg = useConfig() - const { data: walletClient } = useWalletClient() - const [simpleWalletAddress, setSimpleWalletAddress] = useState( - localStorage.getItem(storageKeys.simpleWalletAddress) - ) - const [guardianEmail, setGuardianEmail] = useState( - localStorage.getItem(storageKeys.guardianEmail) - ); - // TODO TEST, probably don't show on FE - const [accountCode, setAccountCode] = useState( - localStorage.getItem(storageKeys.accountCode) - ); - const [gurdianRequestId, setGuardianRequestId] = useState() + const { address } = useAccount() + + const { guardianEmail } = useAppContext() + const [newOwner, setNewOwner] = useState() - const { data: simpleWalletOwner } = useReadContract({ - address: simpleWalletAddress as HexStr, - abi: simpleWalletAbi, - functionName: 'owner', + // TODO pull from recovery module + // const { data: timelock } = useReadContract({ + // address: simpleWalletAddress as HexStr, + // abi: simpleWalletAbi, + // functionName: 'timelock', + // }); + const timelock = -1 + + const { data: recoveryRouterAddr } = useReadContract({ + abi: recoveryPluginAbi, + address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`, + functionName: 'getRouterForSafe', + args: [address] }); - const { data: timelock } = useReadContract({ - address: simpleWalletAddress as HexStr, - abi: simpleWalletAbi, - functionName: 'timelock', - }); - - const deploySimpleWallet = useCallback(async() => { - const simpleWalletInterface = new ethers.Interface(simpleWalletAbi); - const data = simpleWalletInterface.encodeFunctionData('initialize', [ - walletClient?.account.address, verifier, dkimRegistry, emailAuthImpl - ]); - - const hash = await walletClient?.deployContract({ - abi: proxyAbi, - bytecode: proxyBytecode.object as HexStr, - args: [simpleWalletImpl, data], - }) as HexStr - const { contractAddress } = await waitForTransactionReceipt(cfg, { hash }) - if (!contractAddress) { - throw new Error('simplewallet deployment has no contractAddress'); - } - - setSimpleWalletAddress(contractAddress); - // localStorage.setItem(storageKeys.simpleWalletAddress, contractAddress); - }, [walletClient, cfg]) - - const requestGuardian = useCallback(async () => { - if (!simpleWalletAddress) { - throw new Error('simple wallet address not set') - } - - if (!guardianEmail) { - throw new Error('guardian email not set') - } - - const accountCode = await genAccountCode() - const subject = getRequestGuardianSubject(simpleWalletAddress); - - const { requestId } = await relayer.acceptanceRequest( - simpleWalletAddress, - guardianEmail, - accountCode, - templateIdx, - subject, - ); - - setGuardianRequestId(requestId) - - setAccountCode(accountCode) - // localStorage.setItem(storageKeys.accountCode, accountCode) - // localStorage.setItem(storageKeys.guardianEmail, guardianEmail) - }, [simpleWalletAddress, guardianEmail]) - - 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 requestRecovery = useCallback(async () => { - if (!simpleWalletAddress) { - throw new Error('simple wallet address not set') + if (!address) { + throw new Error('unable to get account address'); } if (!guardianEmail) { @@ -126,60 +45,50 @@ export function PerformRecovery() { throw new Error('new owner not set') } - const subject = getRequestsRecoverySubject(simpleWalletAddress, newOwner) + if (!recoveryRouterAddr) { + throw new Error('could not find recovery router for safe') + } + + const subject = getRequestsRecoverySubject(address, newOwner) const { requestId } = await relayer.recoveryRequest( - simpleWalletAddress, + recoveryRouterAddr as string, guardianEmail, templateIdx, subject, ) console.debug('recovery request id', requestId) - }, [simpleWalletAddress, guardianEmail, newOwner]) + }, [recoveryRouterAddr, address, guardianEmail, newOwner]) const completeRecovery = useCallback(async () => { - // TODO Instead, poll relayer.requestStatus until complete recovery is complete + if (!recoveryRouterAddr) { + throw new Error('could not find recovery router for safe') + } - }, []); + console.debug('recovery router addr', recoveryRouterAddr); + const res = relayer.completeRecovery( + recoveryRouterAddr as string + ); + + console.debug('complete recovery res', res) + }, [recoveryRouterAddr]); return ( <> -
{`TEST SimplerWallet address: ${simpleWalletAddress}`}
-
{`TEST SimpleWallet owner ${simpleWalletOwner}`}
-
{`TEST account code: ${accountCode}`}
-
{`TEST timelock: ${timelock}`}
- -
- - -
-
- -
- + + +
{`TEST timelock: ${timelock}`}
); diff --git a/packages/demos/email-recovery/src/context/AppContext.tsx b/packages/demos/email-recovery/src/context/AppContext.tsx new file mode 100644 index 0000000..5924b24 --- /dev/null +++ b/packages/demos/email-recovery/src/context/AppContext.tsx @@ -0,0 +1,16 @@ +import { createContext } from 'react' + +type AppContextType = { + accountCode: string, + setAccountCode: (ac: string) => void; + guardianEmail: string; + setGuardianEmail: (ge: string) => void; +} + +export const appContext = createContext({ + accountCode: '', + setAccountCode: () => {}, + guardianEmail: '', + setGuardianEmail: () => {} +}); + diff --git a/packages/demos/email-recovery/src/context/AppContextHook.ts b/packages/demos/email-recovery/src/context/AppContextHook.ts new file mode 100644 index 0000000..51dbebf --- /dev/null +++ b/packages/demos/email-recovery/src/context/AppContextHook.ts @@ -0,0 +1,4 @@ +import { useContext } from "react"; +import { appContext } from "./AppContext"; + +export const useAppContext = () => useContext(appContext) diff --git a/packages/demos/email-recovery/src/context/AppContextProvider.tsx b/packages/demos/email-recovery/src/context/AppContextProvider.tsx new file mode 100644 index 0000000..52836b2 --- /dev/null +++ b/packages/demos/email-recovery/src/context/AppContextProvider.tsx @@ -0,0 +1,23 @@ +import { ReactNode, useMemo, useState } from "react"; +import { appContext } from "./AppContext"; + +export const AppContextProvider = ({ children } : { children: ReactNode }) => { + const [accountCode, setAccountCode] = useState(''); + const [guardianEmail, setGuardianEmail] = useState(''); + + const ctxVal = useMemo(() => ({ + accountCode, + setAccountCode, + guardianEmail, + setGuardianEmail, + }), [ + accountCode, + guardianEmail + ]) + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/packages/demos/email-recovery/src/services/relayer.ts b/packages/demos/email-recovery/src/services/relayer.ts index 42aa89c..911ca46 100644 --- a/packages/demos/email-recovery/src/services/relayer.ts +++ b/packages/demos/email-recovery/src/services/relayer.ts @@ -2,10 +2,6 @@ import axios from "axios" // Spec: https://www.notion.so/proofofemail/Email-Sender-Auth-c87063cd6cdc4c5987ea3bc881c68813#d7407d31e1354167be61612f5a16995b // TODO Consider using a bigint for templateIdx as it *could* overflow JS number, but practically seems unlikely -type RequestIDData = { - request_id: number; -} - class Relayer { private readonly apiRoute = 'api'; apiUrl: string; @@ -62,9 +58,7 @@ class Relayer { templateIdx: number, subject: string ) { - const { - request_id: requestId - } = await axios({ + const { data } = await axios({ method: "POST", url: `${this.apiUrl}/recoveryRequest`, data: { @@ -74,13 +68,14 @@ class Relayer { subject, } }) + const { request_id: requestId } = data return { requestId }; } - async completeRequest(walletEthAddr: string) { - const data = await axios({ + async completeRecovery(walletEthAddr: string) { + const data = await axios({ method: "POST", - url: `${this.apiUrl}/completeRequest`, + url: `${this.apiUrl}/completeRecovery`, data: { wallet_eth_addr: walletEthAddr, } @@ -89,7 +84,7 @@ class Relayer { } async getAccountSalt(accountCode: string, emailAddress: string) { - const { data } = await axios({ + const { data } = await axios({ method: "POST", url: `${this.apiUrl}/getAccountSalt`, data: { diff --git a/packages/demos/email-recovery/src/utils/email.ts b/packages/demos/email-recovery/src/utils/email.ts index 7d3b7ee..dbd4e90 100644 --- a/packages/demos/email-recovery/src/utils/email.ts +++ b/packages/demos/email-recovery/src/utils/email.ts @@ -51,4 +51,4 @@ export async function genAccountCode(): Promise { export const getRequestGuardianSubject = (acctAddr: string) => `Accept guardian request for ${acctAddr}`; export const getRequestsRecoverySubject = (acctAddr: string, newOwner: string) => - `Set the new signer of ${acctAddr} to ${newOwner}`; + `Update owner to ${newOwner} on account ${acctAddr}`;