Skip to content

Commit

Permalink
Setup deploy sub-steps
Browse files Browse the repository at this point in the history
Progress on deployer funding
  • Loading branch information
jmrossy committed Dec 22, 2024
1 parent 2cb436c commit 9b46410
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 42 deletions.
22 changes: 0 additions & 22 deletions src/features/deployerWallet/create.ts

This file was deleted.

39 changes: 39 additions & 0 deletions src/features/deployerWallet/fund.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { MultiProtocolProvider } from '@hyperlane-xyz/sdk';
import { Numberish } from '@hyperlane-xyz/utils';
import { useAccounts, useActiveChains, useTransactionFns } from '@hyperlane-xyz/widgets';
import { useMutation } from '@tanstack/react-query';
import { Wallet } from 'ethers';
import { useMultiProvider } from '../chains/hooks';

export function useFundDeployerAccount(deployer: Wallet, chainName: ChainName, amount: Numberish) {
const multiProvider = useMultiProvider();
const activeAccounts = useAccounts(multiProvider);
const activeChains = useActiveChains(multiProvider);
const transactionFns = useTransactionFns(multiProvider);

const { isPending, mutateAsync, error } = useMutation({
mutationKey: ['fundDeployerAccount', deployer.address, chainName, amount],
mutationFn: () =>
executeTransfer({ multiProvider, activeAccounts, activeChains, transactionFns }),
});

return {
fund: mutateAsync,
isPending,
error,
};
}

async function executeTransfer({
multiProvider,
activeAccounts,
activeChains,
transactionFns,
}: {
multiProvider: MultiProtocolProvider;
activeAccounts: ReturnType<typeof useAccounts>;
activeChains: ReturnType<typeof useActiveChains>;
transactionFns: ReturnType<typeof useTransactionFns>;
}) {
//TODO
}
35 changes: 35 additions & 0 deletions src/features/deployerWallet/manage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { utils, Wallet } from 'ethers';
import { config } from '../../consts/config';
import { logger } from '../../utils/logger';
import { getTempDeployerWallet, setTempDeployerWallet } from './storage';

function createTempDeployerWallet(): Wallet {
logger.info('Creating temp deployer wallet');
const entropy = utils.randomBytes(32);
const mnemonic = utils.entropyToMnemonic(entropy);
logger.info('Temp deployer wallet created');
return Wallet.fromMnemonic(mnemonic);
}

export async function getOrCreateTempDeployerWallet(): Promise<Wallet> {
try {
const existingWallet = await getTempDeployerWallet(
config.tempWalletEncryptionKey,
config.tempWalletEncryptionSalt,
);
if (existingWallet) {
logger.info('Using existing wallet from storage');
return existingWallet;
} else {
const newWallet = createTempDeployerWallet();
await setTempDeployerWallet(
newWallet,
config.tempWalletEncryptionKey,
config.tempWalletEncryptionSalt,
);
return newWallet;
}
} catch (error) {
throw new Error('Error preparing temp deployer wallet', { cause: error });
}
}
11 changes: 8 additions & 3 deletions src/features/deployerWallet/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ export function hasTempDeployerWallet(): boolean {
export async function getTempDeployerWallet(
password: string,
salt: string,
): Promise<Wallet | null> {
logger.debug('Reading temp deployer wallet from local storage');
): Promise<Wallet | undefined> {
logger.debug('Checking temp deployer wallet from local storage');
const encryptedMnemonic = localStorage.getItem(STORAGE_PATH);
if (!encryptedMnemonic) return null;
if (!encryptedMnemonic) {
logger.debug('No temp deployer wallet found in local storage');
return undefined;
} else {
logger.debug('Found temp deployer wallet in local storage');
}
const mnemonic = await decryptString(encryptedMnemonic, password, salt);
return Wallet.fromMnemonic(mnemonic);
}
Expand Down
112 changes: 95 additions & 17 deletions src/features/deployment/warp/WarpDeploymentDeploy.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import { ArrowIcon, Button, Modal, useModal, WalletIcon } from '@hyperlane-xyz/widgets';
import { useState } from 'react';
import {
ArrowIcon,
Button,
Modal,
SpinnerIcon,
useModal,
WalletIcon,
} from '@hyperlane-xyz/widgets';
import { useQuery } from '@tanstack/react-query';
import { Wallet } from 'ethers';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { PlanetSpinner } from '../../../components/animation/PlanetSpinner';
import { SolidButton } from '../../../components/buttons/SolidButton';
Expand All @@ -12,8 +21,16 @@ import { useCardNav } from '../../../flows/hooks';
import { Color } from '../../../styles/Color';
import { useMultiProvider } from '../../chains/hooks';
import { getChainDisplayName } from '../../chains/utils';
import { getOrCreateTempDeployerWallet } from '../../deployerWallet/manage';
import { useWarpDeploymentConfig } from '../hooks';

enum DeployStep {
PrepDeployer,
FundDeployer,
ExecuteDeploy,
AddFunds,
}

export function WarpDeploymentDeploy() {
return (
<div className="flex w-full flex-col items-center space-y-5 py-2 xs:min-w-100">
Expand All @@ -32,24 +49,86 @@ function MainSection() {
// TODO remove?
// const { deploymentConfig } = useWarpDeploymentConfig();
// const _chains = deploymentConfig?.chains || [];
const { setPage } = useCardNav();

const [isDeploying, _setIsDeploying] = useState(false);
const [isOutOfFunds] = useState(true);
const [step, setStep] = useState(DeployStep.PrepDeployer);
const [deployer, setDeployer] = useState<Wallet | undefined>(undefined);

const onDeployerReady = (wallet: Wallet) => {
setDeployer(wallet);
setStep(DeployStep.FundDeployer);
};

const onDeployerFunded = () => {
setStep(DeployStep.ExecuteDeploy);
};

const onFailure = (error: Error) => {
// TODO carry error over via store state
toast.error(error.message);
setPage(CardPage.WarpFailure);
};

return (
<div className="space-y-3">
{isDeploying ? <DeployStatus /> : isOutOfFunds ? <FundSingleAccount /> : <FundAccounts />}
{step === DeployStep.PrepDeployer && (
<PrepDeployerAccounts onSuccess={onDeployerReady} onFailure={onFailure} />
)}
{step === DeployStep.FundDeployer && deployer && (
<FundDeployerAccounts
deployer={deployer}
onSuccess={onDeployerFunded}
onFailure={onFailure}
/>
)}
{step === DeployStep.ExecuteDeploy && deployer && <ExecuteDeploy />}
{step === DeployStep.AddFunds && deployer && <FundSingleDeployerAccount />}
</div>
);
}

function FundAccounts() {
function PrepDeployerAccounts({
onSuccess,
onFailure,
}: {
onSuccess: (wallet: Wallet) => void;
onFailure: (error: Error) => void;
}) {
const { error, data } = useQuery({
queryKey: ['getDeployerWallet'],
queryFn: getOrCreateTempDeployerWallet,
});

useEffect(() => {
if (error) return onFailure(error);
if (data) return onSuccess(data);
}, [error, data, onSuccess, onFailure]);

return (
<div className="flex flex-col items-center gap-6 py-14">
<SpinnerIcon width={48} height={48} color={Color.primary['500']} />
<p className="text-center text-sm text-gray-700">Preparing deployer accounts</p>
</div>
);
}

function FundDeployerAccounts({
deployer,
onSuccess,
onFailure,
}: {
deployer: Wallet;
onSuccess: () => void;
onFailure: (error: Error) => void;
}) {
const multiProvider = useMultiProvider();
const { deploymentConfig } = useWarpDeploymentConfig();
const chains = deploymentConfig?.chains || [];
const numChains = chains.length;

const [currentChainIndex, setCurrentChainIndex] = useState(0);
const currentChain = chains[currentChainIndex];
const currentChainDisplay = getChainDisplayName(multiProvider, currentChain, true);

const onClickFund = () => {
// TODO create a temp deployer account and trigger a
Expand All @@ -59,22 +138,21 @@ function FundAccounts() {
};

return (
<div className="flex flex-col items-center space-y-5 py-4">
<div className="flex flex-col items-center space-y-6 py-4">
<FundIcons color={Color.primary['500']} />
<p className="max-w-sm text-center text-sm leading-relaxed">
To deploy your route, a temporary account must be funded for each chain. Unused amounts are
refunded.
<p className="max-w-sm text-center text-md leading-relaxed">
To deploy, a temporary account must be funded for each chain. Unused amounts are refunded.
</p>
<SolidButton
color="accent"
className="px-3 py-1.5 text-sm"
className="px-3 py-1.5 text-md"
onClick={onClickFund}
>{`Fund on ${currentChain} (Chain ${currentChainIndex + 1} / ${numChains})`}</SolidButton>
>{`Fund on ${currentChainDisplay} (Chain ${currentChainIndex + 1} / ${numChains})`}</SolidButton>
</div>
);
}

function FundSingleAccount() {
function FundSingleDeployerAccount() {
const onClickFund = () => {
// TODO transfers funds from wallet to deployer
};
Expand All @@ -98,14 +176,14 @@ function FundSingleAccount() {
function FundIcons({ color }: { color: string }) {
return (
<div className="flex items-center justify-center gap-3">
<WalletIcon width={40} height={40} color={color} />
<ArrowIcon width={30} height={30} color={color} direction="e" />
<GasIcon width={38} height={38} color={color} />
<WalletIcon width={44} height={44} color={color} />
<ArrowIcon width={20} height={20} color={color} direction="e" />
<GasIcon width={42} height={42} color={color} />
</div>
);
}

function DeployStatus() {
function ExecuteDeploy() {
const multiProvider = useMultiProvider();
const { deploymentConfig } = useWarpDeploymentConfig();
const chains = deploymentConfig?.chains || [];
Expand Down

0 comments on commit 9b46410

Please sign in to comment.