From 483ab12219f05ced62f128dcf44ca332320bf19e Mon Sep 17 00:00:00 2001 From: Wieedze Date: Tue, 28 Apr 2026 08:42:15 +0200 Subject: [PATCH 1/9] feat(webapp): Safe co-admin shortcut on Deploy + Docs link from banners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two small UX wins around the Safe story: Deploy form (packages/webapp/src/pages/Deploy.tsx): - "+ Add my wallet" button (when connected) — appends the EOA to the admins textarea - "+ Add Safe address" toggle — opens an inline input with an Add button that validates checksummed-address before appending - appendAdmin helper handles dedup so the same address can't land twice - Existing "Heads up" hint now links to /docs/safe-admin Removes friction for the recommended pattern (EOA + Safe co-admins from genesis) — users no longer have to copy-paste two addresses. ProxyAdminSafeBanner (packages/webapp/src/components/): - Both EOA banners (mainnet rose + dev amber) now link to /docs/safe-admin via react-router Link - "Safe admin guide" call-to-action is the actionable next step for someone who lands on the warning Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/components/ProxyAdminSafeBanner.tsx | 19 +++++- packages/webapp/src/pages/Deploy.tsx | 67 ++++++++++++++++++- 2 files changed, 81 insertions(+), 5 deletions(-) diff --git a/packages/webapp/src/components/ProxyAdminSafeBanner.tsx b/packages/webapp/src/components/ProxyAdminSafeBanner.tsx index 8f86c95..744b6fd 100644 --- a/packages/webapp/src/components/ProxyAdminSafeBanner.tsx +++ b/packages/webapp/src/components/ProxyAdminSafeBanner.tsx @@ -1,3 +1,4 @@ +import { Link } from 'react-router-dom' import type { Address } from 'viem' import { useChainId } from 'wagmi' import { useSafeStatus } from '../hooks/useSafeStatus' @@ -47,14 +48,28 @@ export function ProxyAdminSafeBanner({ proxyAdmin }: Props) { if (onMainnet) { return (
- EOA proxyAdmin on mainnet — high risk. This single key can swap the proxy's implementation, replacing the entire logic of the contract. A key compromise here means total loss of control. Rotate to a Gnosis Safe before any production use. + EOA proxyAdmin on mainnet — high risk. This single key can swap the proxy's implementation, replacing the entire logic of the contract. A key compromise here means total loss of control. Rotate to a Gnosis Safe before any production use.{' '} + + Read the Safe admin guide + + .
) } return (
- EOA proxyAdmin. Fine for dev / testing. Rotate to a Safe before this proxy goes near mainnet. + EOA proxyAdmin. Fine for dev / testing. Rotate to a Safe before this proxy goes near mainnet.{' '} + + Safe admin guide + + .
) } diff --git a/packages/webapp/src/pages/Deploy.tsx b/packages/webapp/src/pages/Deploy.tsx index 4185bf1..536e7d6 100644 --- a/packages/webapp/src/pages/Deploy.tsx +++ b/packages/webapp/src/pages/Deploy.tsx @@ -20,7 +20,7 @@ import { Spinner } from '../components/Spinner' const FEE_DENOMINATOR = 10_000n export default function DeployPage() { - const { isConnected } = useAccount() + const { address: connectedAddress, isConnected } = useAccount() const chainId = useChainId() const network = networkFor(chainId) const navigate = useNavigate() @@ -33,6 +33,18 @@ export default function DeployPage() { const [fixedFeeEth, setFixedFeeEth] = useState('0.1') const [percentageBps, setPercentageBps] = useState('500') const [adminsRaw, setAdminsRaw] = useState('') + const [safeInput, setSafeInput] = useState('') + const [showSafeInput, setShowSafeInput] = useState(false) + + function appendAdmin(addr: string): void { + setAdminsRaw((prev) => { + const trimmed = prev.trim() + if (!trimmed) return addr + const existing = trimmed.split(/[,\s\n]+/).map((a) => a.toLowerCase()) + if (existing.includes(addr.toLowerCase())) return prev + return `${trimmed}\n${addr}` + }) + } const { deploy, hash, isPending, error, factory } = useDeployProxy() const receipt = useWaitForTransactionReceipt({ hash }) @@ -306,6 +318,47 @@ export default function DeployPage() { label="Admins" hint="One address per line or comma-separated. At least one required." > +
+ {connectedAddress && ( + + )} + +
+ {showSafeInput && ( +
+ setSafeInput(e.target.value)} + placeholder="0x… (Gnosis Safe address)" + className="input font-mono text-xs flex-1 min-w-[260px]" + /> + +
+ )}