Skip to content

Commit

Permalink
Implement deployer account funding
Browse files Browse the repository at this point in the history
  • Loading branch information
jmrossy committed Dec 23, 2024
1 parent 9b46410 commit 518d121
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 22 deletions.
10 changes: 5 additions & 5 deletions src/components/toast/useToastError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { logger } from '../../utils/logger';

export function useToastError(error: any, errorMsg?: string) {
export function useToastError(error: any, context: string) {
useEffect(() => {
if (!error) return;
const message = errorMsg || errorToString(error, 500);
logger.error(message, error);
toast.error(errorMsg);
}, [error, errorMsg]);
logger.error(context, error);
const errorMsg = errorToString(error, 150);
toast.error(`${context}: ${errorMsg}`);
}, [error, context]);
}
1 change: 1 addition & 0 deletions src/consts/consts.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const MIN_CHAIN_BALANCE = 1; // 1 Wei
export const WARP_DEPLOY_GAS_UNITS = BigInt(3e7);
113 changes: 105 additions & 8 deletions src/features/deployerWallet/fund.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,136 @@
import { MultiProtocolProvider } from '@hyperlane-xyz/sdk';
import { Numberish } from '@hyperlane-xyz/utils';
import { useAccounts, useActiveChains, useTransactionFns } from '@hyperlane-xyz/widgets';
import {
EvmNativeTokenAdapter,
getChainIdNumber,
MultiProtocolProvider,
ProviderType,
WarpTxCategory,
WarpTypedTransaction,
} from '@hyperlane-xyz/sdk';
import { assert } from '@hyperlane-xyz/utils';
import {
getAccountAddressForChain,
useAccounts,
useActiveChains,
useTransactionFns,
} from '@hyperlane-xyz/widgets';
import { useMutation } from '@tanstack/react-query';
import { Wallet } from 'ethers';
import { toastTxSuccess } from '../../components/toast/TxSuccessToast';
import { useToastError } from '../../components/toast/useToastError';
import { logger } from '../../utils/logger';
import { useMultiProvider } from '../chains/hooks';
import { getChainDisplayName } from '../chains/utils';

export function useFundDeployerAccount(deployer: Wallet, chainName: ChainName, amount: Numberish) {
export function useFundDeployerAccount(deployer: Wallet, chainName: ChainName, gasUnits: bigint) {
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],
mutationKey: ['fundDeployerAccount', deployer.address, chainName, gasUnits],
mutationFn: () =>
executeTransfer({ multiProvider, activeAccounts, activeChains, transactionFns }),
executeTransfer({
deployer,
chainName,
gasUnits,
multiProvider,
activeAccounts,
activeChains,
transactionFns,
}),
});

useToastError(
error,
`Error funding deployer on ${getChainDisplayName(multiProvider, chainName)}`,
);

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

async function executeTransfer({
deployer,
chainName,
gasUnits,
multiProvider,
activeAccounts,
activeChains,
transactionFns,
}: {
deployer: Wallet;
chainName: ChainName;
gasUnits: bigint;
multiProvider: MultiProtocolProvider;
activeAccounts: ReturnType<typeof useAccounts>;
activeChains: ReturnType<typeof useActiveChains>;
transactionFns: ReturnType<typeof useTransactionFns>;
}) {
//TODO
const recipient = deployer.address;
logger.debug('Preparing to fund deployer', recipient, chainName);

const protocol = multiProvider.getProtocol(chainName);
const sendTransaction = transactionFns[protocol].sendTransaction;
const activeChainName = activeChains.chains[protocol].chainName;
const sender = getAccountAddressForChain(multiProvider, chainName, activeAccounts.accounts);
if (!sender) throw new Error(`No active account found for chain ${chainName}`);

const amount = await getFundingAmount(chainName, gasUnits, multiProvider);
await assertSenderBalance(sender, chainName, amount, multiProvider);
const tx = await getFundingTx(recipient, chainName, amount, multiProvider);
const { hash, confirm } = await sendTransaction({
tx,
chainName,
activeChainName,
});

const txReceipt = await confirm();
logger.debug(`Deployer funding tx confirmed on ${chainName}, hash: ${hash}`);
toastTxSuccess(`Deployer funded on ${chainName}!`, hash, origin);
return txReceipt;
}

async function getFundingAmount(
chainName: ChainName,
gasUnits: bigint,
multiProvider: MultiProtocolProvider,
) {
const provider = multiProvider.getViemProvider(chainName);
const gasPrice = await provider.getGasPrice();
return gasUnits * gasPrice;
}

async function assertSenderBalance(
sender: Address,
chainName: ChainName,
amount: bigint,
multiProvider: MultiProtocolProvider,
) {
const adapter = new EvmNativeTokenAdapter(chainName, multiProvider, {});
const balance = await adapter.getBalance(sender);
assert(balance >= amount, 'Insufficient balance for deployment');
}

// TODO edit Widgets lib to default to TypedTransaction instead of WarpTypedTransaction
async function getFundingTx(
recipient: Address,
chainName: ChainName,
amount: bigint,
multiProvider: MultiProtocolProvider,
): Promise<WarpTypedTransaction> {
const adapter = new EvmNativeTokenAdapter(chainName, multiProvider, {});
const tx = await adapter.populateTransferTx({ recipient, weiAmountOrId: amount });
// Add chainId to help reduce likely of wallet signing on wrong chain
const chainId = getChainIdNumber(multiProvider.getChainMetadata(chainName));
// TODO remove data when widgets lib is updated
const txParams = { ...tx, chainId, data: '0x' };
return {
type: ProviderType.EthersV5,
transaction: txParams,
category: WarpTxCategory.Transfer,
};
}
1 change: 1 addition & 0 deletions src/features/deployerWallet/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function hasTempDeployerWallet(): boolean {
return !!localStorage.getItem(STORAGE_PATH);
}

// TODO redo this in a multi-protocol way, otherwise backwards compat may break when it's changed later
export async function getTempDeployerWallet(
password: string,
salt: string,
Expand Down
25 changes: 16 additions & 9 deletions src/features/deployment/warp/WarpDeploymentDeploy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import { GasIcon } from '../../../components/icons/GasIcon';
import { LogsIcon } from '../../../components/icons/LogsIcon';
import { StopIcon } from '../../../components/icons/StopIcon';
import { H1 } from '../../../components/text/Headers';
import { WARP_DEPLOY_GAS_UNITS } from '../../../consts/consts';
import { CardPage } from '../../../flows/CardPage';
import { useCardNav } from '../../../flows/hooks';
import { Color } from '../../../styles/Color';
import { useMultiProvider } from '../../chains/hooks';
import { getChainDisplayName } from '../../chains/utils';
import { useFundDeployerAccount } from '../../deployerWallet/fund';
import { getOrCreateTempDeployerWallet } from '../../deployerWallet/manage';
import { useWarpDeploymentConfig } from '../hooks';

Expand Down Expand Up @@ -75,18 +77,16 @@ function MainSection() {
<PrepDeployerAccounts onSuccess={onDeployerReady} onFailure={onFailure} />
)}
{step === DeployStep.FundDeployer && deployer && (
<FundDeployerAccounts
deployer={deployer}
onSuccess={onDeployerFunded}
onFailure={onFailure}
/>
<FundDeployerAccounts deployer={deployer} onSuccess={onDeployerFunded} />
)}
{step === DeployStep.ExecuteDeploy && deployer && <ExecuteDeploy />}
{step === DeployStep.AddFunds && deployer && <FundSingleDeployerAccount />}
</div>
);
}

// TODO improve smoothness during card flow transition
// Maybe fold this into FundDeployerAccounts to avoid spinner flash
function PrepDeployerAccounts({
onSuccess,
onFailure,
Expand Down Expand Up @@ -115,11 +115,9 @@ function PrepDeployerAccounts({
function FundDeployerAccounts({
deployer,
onSuccess,
onFailure,
}: {
deployer: Wallet;
onSuccess: () => void;
onFailure: (error: Error) => void;
}) {
const multiProvider = useMultiProvider();
const { deploymentConfig } = useWarpDeploymentConfig();
Expand All @@ -130,10 +128,18 @@ function FundDeployerAccounts({
const currentChain = chains[currentChainIndex];
const currentChainDisplay = getChainDisplayName(multiProvider, currentChain, true);

const onClickFund = () => {
// TODO create a temp deployer account and trigger a
const { isPending, triggerTransaction } = useFundDeployerAccount(
deployer,
currentChain,
WARP_DEPLOY_GAS_UNITS,
);

const onClickFund = async () => {
await triggerTransaction();
if (currentChainIndex < numChains - 1) {
setCurrentChainIndex(currentChainIndex + 1);
} else {
onSuccess();
}
};

Expand All @@ -147,6 +153,7 @@ function FundDeployerAccounts({
color="accent"
className="px-3 py-1.5 text-md"
onClick={onClickFund}
disabled={isPending}
>{`Fund on ${currentChainDisplay} (Chain ${currentChainIndex + 1} / ${numChains})`}</SolidButton>
</div>
);
Expand Down

0 comments on commit 518d121

Please sign in to comment.