diff --git a/hyperdrive/src/register-ui/src/App.tsx b/hyperdrive/src/register-ui/src/App.tsx index a6d54658e..90251cf39 100644 --- a/hyperdrive/src/register-ui/src/App.tsx +++ b/hyperdrive/src/register-ui/src/App.tsx @@ -4,6 +4,7 @@ import { Navigate, BrowserRouter as Router, Route, Routes, useParams } from 'rea import CommitDotOsName from "./pages/CommitDotOsName"; import MintDotOsName from "./pages/MintDotOsName"; import MintCustom from "./pages/MintCustom"; +import UpgradeCustom from "./pages/UpgradeCustom"; import SetPassword from "./pages/SetPassword"; import Login from './pages/Login' import ResetName from './pages/ResetName' @@ -24,6 +25,8 @@ function App() { const [reset, setReset] = useState(false); const [direct, setDirect] = useState(false); const [hnsName, setHnsName] = useState(''); + const [tbaAddress, setTbaAddress] = useState(''); + const [implAddress, setImplAddress] = useState(''); const [networkingKey, setNetworkingKey] = useState(''); const [ipAddress, setIpAddress] = useState(0); const [ws_port, setWsPort] = useState(0); @@ -94,6 +97,10 @@ function App() { tcp_port, setTcpPort, routers, setRouters, nodeChainId, + tbaAddress, + setTbaAddress, + implAddress, + setImplAddress } return ( @@ -138,6 +145,12 @@ function App() { } /> + + + + + } /> diff --git a/hyperdrive/src/register-ui/src/abis/index.ts b/hyperdrive/src/register-ui/src/abis/index.ts index 72f8e3ea2..298c49f35 100644 --- a/hyperdrive/src/register-ui/src/abis/index.ts +++ b/hyperdrive/src/register-ui/src/abis/index.ts @@ -31,3 +31,7 @@ export const dotOsAbi = parseAbi([ export const tbaMintAbi = parseAbi([ "function mint(address who, bytes calldata name, bytes calldata initialization, address implementation) external returns (address)" ]); + +export const tbaUpgradeAbi = parseAbi([ + "function upgradeToAndCall(address newImpl, bytes calldata data) external" +]); diff --git a/hyperdrive/src/register-ui/src/lib/types.ts b/hyperdrive/src/register-ui/src/lib/types.ts index a47f5f304..1f279c445 100644 --- a/hyperdrive/src/register-ui/src/lib/types.ts +++ b/hyperdrive/src/register-ui/src/lib/types.ts @@ -21,7 +21,7 @@ export interface PageProps { setReset: React.Dispatch>, pw: string, setPw: React.Dispatch>, - nodeChainId: string, + nodeChainId: string } export type NetworkingInfo = { diff --git a/hyperdrive/src/register-ui/src/pages/HyperdriveHome.tsx b/hyperdrive/src/register-ui/src/pages/HyperdriveHome.tsx index cb1044358..d1bef936c 100644 --- a/hyperdrive/src/register-ui/src/pages/HyperdriveHome.tsx +++ b/hyperdrive/src/register-ui/src/pages/HyperdriveHome.tsx @@ -13,6 +13,7 @@ function HyperdriveHome({ hnsName }: OsHomeProps) { const importKeyfileRedir = () => navigate('/import-keyfile') const loginRedir = () => navigate('/login') const customRegisterRedir = () => navigate('/custom-register') + const customUpgradeRedir = () => navigate('/custom-upgrade') const previouslyBooted = Boolean(hnsName) useEffect(() => { @@ -49,6 +50,9 @@ function HyperdriveHome({ hnsName }: OsHomeProps) { + )} diff --git a/hyperdrive/src/register-ui/src/pages/UpgradeCustom.tsx b/hyperdrive/src/register-ui/src/pages/UpgradeCustom.tsx new file mode 100644 index 000000000..d5e7dac9b --- /dev/null +++ b/hyperdrive/src/register-ui/src/pages/UpgradeCustom.tsx @@ -0,0 +1,179 @@ +import { useState, useEffect, FormEvent, useCallback } from "react"; +import Loader from "../components/Loader"; +import { PageProps } from "../lib/types"; +import { useAccount, useWaitForTransactionReceipt, useSendTransaction } from "wagmi"; +import { useConnectModal, useAddRecentTransaction } from "@rainbow-me/rainbowkit"; +import { tbaUpgradeAbi } from "../abis"; +import { encodeFunctionData, stringToHex } from "viem"; +import BackButton from "../components/BackButton"; + +interface UpgradeCustomNameProps extends PageProps { } + +function UpgradeCustom({ }: UpgradeCustomNameProps) { + const { address } = useAccount(); + const { openConnectModal } = useConnectModal(); + + const [tbaAddress, setTbaAddress] = useState(""); + const [implAddress, setImplAddress] = useState(""); + const [showSuccess, setShowSuccess] = useState(false); + + const { data: hash, sendTransaction, isPending, isError, error } = useSendTransaction({ + mutation: { + onSuccess: (data) => { + addRecentTransaction({ hash: data, description: `Upgrade implementation` }); + }, + }, + }); + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ hash }); + + const addRecentTransaction = useAddRecentTransaction(); + + useEffect(() => { + document.title = "Upgrade Hyper Account"; + }, []); + + useEffect(() => { + if (!address) { + openConnectModal?.(); + } + }, [address, openConnectModal]); + + const handleUpgrade = useCallback( + async (e: FormEvent) => { + e.preventDefault(); + e.stopPropagation(); + + if (!address) { + openConnectModal?.(); + return; + } + + const tba = tbaAddress as `0x${string}`; + const impl = implAddress as `0x${string}`; + + const data = encodeFunctionData({ + abi: tbaUpgradeAbi, + functionName: "upgradeToAndCall", + args: [impl, stringToHex("")], + }); + + try { + sendTransaction({ + to: tba, + data, + gas: 1000000n, + }); + } catch (error) { + console.error("Failed to send transaction:", error); + } + }, + [address, sendTransaction, openConnectModal, tbaAddress, implAddress], + ); + + const isFormValid = tbaAddress.trim() !== "" && implAddress.trim() !== ""; + + // show success screen and reset form on confirmation + useEffect(() => { + if (isConfirmed) { + setTbaAddress(""); + setImplAddress(""); + setShowSuccess(true); + } + }, [isConfirmed]); + + const handleUpgradeNew = () => { + setShowSuccess(false); + }; + + return ( +
+
+
+ {isPending || isConfirming ? ( + + ) : showSuccess ? ( + <> +

+ ✅ Upgrade Successful! +

+

+ Your Hyper Account has been successfully upgraded. +

+
+ + +
+ + ) : ( + <> +

+ Upgrade Hyper Account Implementation +

+ +
+

+ What is "Upgrade Hyper Account"? +

+

+ When minting a Hyper Account, you can make it upgradable. This allows the operator + to update the contract implementation and add or remove various functions without + changing the account address. +

+

+ This page allows you to upgrade an existing upgradable Hyper Account (TBA - Token Bound Account) + to a new implementation contract. The new implementation can include additional features, + bug fixes, or optimizations. +

+

+ ⚠️ In the default implementation, only the operator can call upgradeToAndCall. + This operation requires ERC-1967 support in both the account and implementation contracts. +

+
+ + setTbaAddress(e.target.value)} + /> + setImplAddress(e.target.value)} + /> + +
+ + +
+ + )} + {isError && !showSuccess && ( +

+ Error: {error?.message || "There was an error on upgrade"} +

+ )} + +
+
+ ); +} + +export default UpgradeCustom; \ No newline at end of file diff --git a/hyperdrive/src/register.rs b/hyperdrive/src/register.rs index a0fa844b8..7342fe7cf 100644 --- a/hyperdrive/src/register.rs +++ b/hyperdrive/src/register.rs @@ -115,6 +115,7 @@ pub async fn register( .or(warp::path("import-keyfile")) .or(warp::path("set-password")) .or(warp::path("custom-register")) + .or(warp::path("custom-upgrade")) .and(warp::get()) .map(move |_| warp::reply::html(include_str!("register-ui/build/index.html"))); #[cfg(target_os = "windows")] @@ -127,6 +128,7 @@ pub async fn register( .or(warp::path("import-keyfile")) .or(warp::path("set-password")) .or(warp::path("custom-register")) + .or(warp::path("custom-upgrade")) .and(warp::get()) .map(move |_| warp::reply::html(include_str!("register-ui\\build\\index.html")));