mirror of
https://github.com/getwax/wax.git
synced 2026-01-08 22:57:58 -05:00
Check if connected account is Safe
Remove unusued testing components. Add safe deployment npm lib to get active safe deployments. Will need to update other abi/addr refs to it.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@safe-global/safe-deployments": "^1.35.0",
|
||||
"@tanstack/react-query": "^5.28.14",
|
||||
"@wagmi/cli": "^2.1.4",
|
||||
"axios": "^1.6.8",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import { createContext, useState } from "react";
|
||||
import "./App.css";
|
||||
import ConnectWallets from "./components/ConnectWallets";
|
||||
import Navbar from "./components/Navbar";
|
||||
@@ -8,8 +8,6 @@ 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);
|
||||
|
||||
1
packages/demos/email-recovery/src/abi/IProxy.json
Normal file
1
packages/demos/email-recovery/src/abi/IProxy.json
Normal file
File diff suppressed because one or more lines are too long
1
packages/demos/email-recovery/src/abi/SafeProxy.json
Normal file
1
packages/demos/email-recovery/src/abi/SafeProxy.json
Normal file
File diff suppressed because one or more lines are too long
@@ -1,173 +0,0 @@
|
||||
import { useState, useCallback, useMemo } from 'react'
|
||||
import { useAccount, useWriteContract, useReadContract } from 'wagmi'
|
||||
import { abi as safeAbi } from '../abi/Safe.json'
|
||||
import { abi as recoveryPluginAbi } from '../abi/SafeZkEmailRecoveryPlugin.json'
|
||||
import { safeZkSafeZkEmailRecoveryPlugin } from '../../contracts.base-sepolia.json'
|
||||
import { Button } from './Button'
|
||||
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 { useAppContext } from '../context/AppContextHook'
|
||||
|
||||
export function ConfigureSafeModule() {
|
||||
const { address } = useAccount()
|
||||
const { writeContractAsync } = useWriteContract()
|
||||
|
||||
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)
|
||||
|
||||
const { data: isModuleEnabled } = useReadContract({
|
||||
address,
|
||||
abi: safeAbi,
|
||||
functionName: 'isModuleEnabled',
|
||||
args: [safeZkSafeZkEmailRecoveryPlugin]
|
||||
});
|
||||
|
||||
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 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');
|
||||
}
|
||||
|
||||
await writeContractAsync({
|
||||
abi: safeAbi,
|
||||
address,
|
||||
functionName: 'enableModule',
|
||||
args: [safeZkSafeZkEmailRecoveryPlugin],
|
||||
})
|
||||
}, [address, writeContractAsync])
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
await writeContractAsync({
|
||||
abi: recoveryPluginAbi,
|
||||
address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
|
||||
functionName: 'configureRecovery',
|
||||
args: [
|
||||
firstSafeOwner,
|
||||
guardianAddr,
|
||||
recoveryDelay,
|
||||
previousOwnerInLinkedList
|
||||
],
|
||||
})
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
console.debug('req guard req id', requestId)
|
||||
// TODO poll until guard req is complete or fails
|
||||
}, [
|
||||
address,
|
||||
firstSafeOwner,
|
||||
guardianEmail,
|
||||
recoveryDelay,
|
||||
accountCode,
|
||||
setAccountCode,
|
||||
writeContractAsync
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
isModuleEnabled ?
|
||||
<div>Recovery Module Enabled</div> :
|
||||
<Button onClick={enableEmailRecoveryModule}>
|
||||
1. Enable Email Recovery Module
|
||||
</Button>
|
||||
}
|
||||
<div>
|
||||
<label>
|
||||
Guardian's Email
|
||||
<input disabled ={!isModuleEnabled}
|
||||
type='email'
|
||||
onInput={e => setGuardianEmail((e.target as HTMLTextAreaElement).value)}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Recovery Delay
|
||||
<input
|
||||
disabled={!isModuleEnabled}
|
||||
type='number'
|
||||
onInput={e => setRecoveryDelay(parseInt((e.target as HTMLTextAreaElement).value))}
|
||||
/>
|
||||
</label>
|
||||
<Button
|
||||
disabled={!isModuleEnabled}
|
||||
onClick={configureRecoveryAndRequestGuardian}>
|
||||
2. Configure Recovery & Request Guardian
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { Button } from './Button'
|
||||
import { relayer } from '../services/relayer'
|
||||
import { abi as recoveryPluginAbi } from '../abi/SafeZkEmailRecoveryPlugin.json'
|
||||
import { useReadContract, useAccount } from 'wagmi'
|
||||
import {
|
||||
getRequestsRecoverySubject,
|
||||
templateIdx
|
||||
} from '../utils/email'
|
||||
import { safeZkSafeZkEmailRecoveryPlugin } from '../../contracts.base-sepolia.json'
|
||||
import { useAppContext } from '../context/AppContextHook'
|
||||
|
||||
export function PerformRecovery() {
|
||||
const { address } = useAccount()
|
||||
|
||||
const { guardianEmail } = useAppContext()
|
||||
|
||||
const [newOwner, setNewOwner] = useState<string>()
|
||||
|
||||
// TODO pull from recovery module
|
||||
// const { data: timelock } = useReadContract({
|
||||
// address: simpleWalletAddress as HexStr,
|
||||
// abi: simpleWalletAbi,
|
||||
// functionName: 'timelock',
|
||||
// });
|
||||
|
||||
const { data: recoveryRouterAddr } = useReadContract({
|
||||
abi: recoveryPluginAbi,
|
||||
address: safeZkSafeZkEmailRecoveryPlugin as `0x${string}`,
|
||||
functionName: 'getRouterForSafe',
|
||||
args: [address]
|
||||
});
|
||||
|
||||
const requestRecovery = useCallback(async () => {
|
||||
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,
|
||||
)
|
||||
console.debug('recovery request id', requestId)
|
||||
|
||||
}, [recoveryRouterAddr, address, guardianEmail, newOwner])
|
||||
|
||||
const completeRecovery = useCallback(async () => {
|
||||
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 (
|
||||
<>
|
||||
<label>
|
||||
New Owner (address)
|
||||
<input type='text'
|
||||
onInput={e => setNewOwner((e.target as HTMLTextAreaElement).value)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Button onClick={requestRecovery}>
|
||||
3. Request Recovery
|
||||
</Button>
|
||||
{/* <div>{`TEST timelock: ${timelock}`}</div> */}
|
||||
<Button onClick={completeRecovery}>
|
||||
TEST Complete Recovery (Switch to polling)
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
import { ConnectKitButton } from "connectkit";
|
||||
import { Button } from "./Button";
|
||||
import { useAccount, useReadContract, useWriteContract } from "wagmi";
|
||||
import { useAccount, useBytecode, useChainId, useReadContract, useWriteContract } from "wagmi";
|
||||
import { safeZkSafeZkEmailRecoveryPlugin } from "../../contracts.base-sepolia.json";
|
||||
import { abi as safeAbi } from "../abi/Safe.json";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { abi as proxyAbi } from "../abi/IProxy.json";
|
||||
import { useCallback, useContext, useEffect, useState, useMemo } from "react";
|
||||
import { StepsContext } from "../App";
|
||||
import { STEPS } from "../constants";
|
||||
import { getSafeL2SingletonDeployment } from "@safe-global/safe-deployments";
|
||||
|
||||
const SafeModuleRecovery = () => {
|
||||
const chainId = useChainId();
|
||||
const { address } = useAccount();
|
||||
const { writeContractAsync } = useWriteContract();
|
||||
const stepsContext = useContext(StepsContext);
|
||||
@@ -19,6 +22,18 @@ const SafeModuleRecovery = () => {
|
||||
}
|
||||
}, [address, stepsContext]);
|
||||
|
||||
// See https://ethereum.stackexchange.com/a/141258
|
||||
const { data: addressBytecode } = useBytecode({ address });
|
||||
const { data: masterCopy } = useReadContract({
|
||||
address,
|
||||
abi: proxyAbi,
|
||||
functionName: "masterCopy",
|
||||
});
|
||||
const isSafeAccount = useMemo(() => {
|
||||
const safeL2SingletonAddr = getSafeL2SingletonDeployment({ network: `${chainId}` });
|
||||
return addressBytecode && masterCopy === safeL2SingletonAddr;
|
||||
}, [chainId, addressBytecode, masterCopy])
|
||||
|
||||
const { data: isModuleEnabled } = useReadContract({
|
||||
address,
|
||||
abi: safeAbi,
|
||||
@@ -26,8 +41,6 @@ const SafeModuleRecovery = () => {
|
||||
args: [safeZkSafeZkEmailRecoveryPlugin],
|
||||
});
|
||||
|
||||
console.log(isModuleEnabled);
|
||||
|
||||
if (isModuleEnabled) {
|
||||
console.log("Module is enabled");
|
||||
setLoading(false);
|
||||
@@ -53,11 +66,14 @@ const SafeModuleRecovery = () => {
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
||||
Connected wallet: <ConnectKitButton />
|
||||
</div>
|
||||
{!isModuleEnabled ? (
|
||||
{!isSafeAccount && (
|
||||
<div>Connected account is not a Safe, please connect a Safe</div>
|
||||
)}
|
||||
{isSafeAccount && !isModuleEnabled && (
|
||||
<Button disabled={loading} onClick={enableEmailRecoveryModule}>
|
||||
Enable Email Recovery Module
|
||||
</Button>
|
||||
) : null}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1589,6 +1589,13 @@
|
||||
"@safe-global/safe-gateway-typescript-sdk" "^3.5.3"
|
||||
viem "^1.0.0"
|
||||
|
||||
"@safe-global/safe-deployments@^1.35.0":
|
||||
version "1.35.0"
|
||||
resolved "https://registry.npmjs.org/@safe-global/safe-deployments/-/safe-deployments-1.35.0.tgz#6930a86a006526a9791ebd2a11cf8f5d8358563b"
|
||||
integrity sha512-Of8WQEcvL5Fm+xxnCDjah6Hkw+sNdzcApQnzr+OsPBxYtZL0RRtbmesypj36oOD8BQmyrH54V8DVN+pYjrfJ9g==
|
||||
dependencies:
|
||||
semver "^7.6.0"
|
||||
|
||||
"@safe-global/safe-gateway-typescript-sdk@^3.5.3":
|
||||
version "3.19.0"
|
||||
resolved "https://registry.npmjs.org/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.19.0.tgz#18637c205c83bfc0a6be5fddbf202d6bb4927302"
|
||||
@@ -5671,7 +5678,7 @@ semver@^6.3.1:
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
|
||||
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
|
||||
|
||||
semver@^7.3.8, semver@^7.5.4:
|
||||
semver@^7.3.8, semver@^7.5.4, semver@^7.6.0:
|
||||
version "7.6.0"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
|
||||
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
|
||||
|
||||
Reference in New Issue
Block a user