Skip to content

Commit

Permalink
Restructure deployer wallet code for multi-protocol
Browse files Browse the repository at this point in the history
  • Loading branch information
jmrossy committed Dec 23, 2024
1 parent 518d121 commit e97766c
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 201 deletions.
6 changes: 3 additions & 3 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, context: string) {
export function useToastError(error: any, context: string, errorLength = 120) {
useEffect(() => {
if (!error) return;
logger.error(context, error);
const errorMsg = errorToString(error, 150);
const errorMsg = errorToString(error, errorLength);
toast.error(`${context}: ${errorMsg}`);
}, [error, context]);
}, [error, context, errorLength]);
}
30 changes: 17 additions & 13 deletions src/features/deployerWallet/fund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,36 @@ import {
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, gasUnits: bigint) {
export function useFundDeployerAccount(
chainName: ChainName,
gasUnits: bigint,
deployerAddress?: Address,
) {
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, gasUnits],
mutationFn: () =>
executeTransfer({
deployer,
mutationKey: ['fundDeployerAccount', deployerAddress, chainName, gasUnits],
mutationFn: () => {
if (!deployerAddress || !chainName || !gasUnits) return Promise.resolve(null);
return executeTransfer({
deployerAddress,
chainName,
gasUnits,
multiProvider,
activeAccounts,
activeChains,
transactionFns,
}),
});
},
});

useToastError(
Expand All @@ -54,24 +59,23 @@ export function useFundDeployerAccount(deployer: Wallet, chainName: ChainName, g
}

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

const protocol = multiProvider.getProtocol(chainName);
const sendTransaction = transactionFns[protocol].sendTransaction;
Expand All @@ -81,7 +85,7 @@ async function executeTransfer({

const amount = await getFundingAmount(chainName, gasUnits, multiProvider);
await assertSenderBalance(sender, chainName, amount, multiProvider);
const tx = await getFundingTx(recipient, chainName, amount, multiProvider);
const tx = await getFundingTx(deployerAddress, chainName, amount, multiProvider);
const { hash, confirm } = await sendTransaction({
tx,
chainName,
Expand Down Expand Up @@ -115,7 +119,7 @@ async function assertSenderBalance(
assert(balance >= amount, 'Insufficient balance for deployment');
}

// TODO edit Widgets lib to default to TypedTransaction instead of WarpTypedTransaction
// TODO edit Widgets lib to default to TypedTransaction instead of WarpTypedTransaction?
async function getFundingTx(
recipient: Address,
chainName: ChainName,
Expand Down
147 changes: 147 additions & 0 deletions src/features/deployerWallet/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { ProviderType } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { useQuery } from '@tanstack/react-query';
import { utils, Wallet } from 'ethers';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { config } from '../../consts/config';
import { logger } from '../../utils/logger';
import { useStore } from '../store';
import { decryptString, encryptString } from './encryption';
import { TempDeployerKeys, TempDeployerWallets, TypedWallet } from './types';

export function useTempDeployerWallets(
protocols: ProtocolType[],
onFailure?: (error: Error) => void,
) {
// const tempDeployerKeys = useStore((s) => s.tempDeployerKeys);
const { tempDeployerKeys, setDeployerKey } = useStore((s) => ({
tempDeployerKeys: s.tempDeployerKeys,
setDeployerKey: s.setDeployerKey,
}));
const { error, isLoading, data } = useQuery({
queryKey: ['getDeployerWallet', protocols, tempDeployerKeys, setDeployerKey],
queryFn: () => getOrCreateTempDeployerWallets(protocols, tempDeployerKeys, setDeployerKey),
retry: false,
staleTime: Infinity,
gcTime: Infinity,
});
useEffect(() => {
if (error) {
toast.error('Error preparing deployer accounts');
if (onFailure) onFailure(error);
}
}, [error, onFailure]);

return {
isLoading,
error,
wallets: data || {},
};
}

// export function useSetTempDeployerWallet(protocols: ProtocolType[]) {
// const setDeployerKey = useStore((s) => s.setDeployerKey);
// }

export function useRemoveTempDeployerWallet(protocols: ProtocolType[]) {
const removeDeployerKey = useStore((s) => s.removeDeployerKey);
return () => protocols.map((p) => removeDeployerKey(p));
}

async function getOrCreateTempDeployerWallets(
protocols: ProtocolType[],
encryptedKeys: TempDeployerKeys,
storeKey: (protocol: ProtocolType, key: string) => void,
): Promise<TempDeployerWallets> {
const wallets: TempDeployerWallets = {};

for (const protocol of protocols) {
try {
const encryptedKey = encryptedKeys[protocol];
if (encryptedKey) {
logger.debug('Found deployer key in store for:', protocol);
const wallet = await getTempDeployerWallet(protocol, encryptedKey);
wallets[protocol] = wallet;
} else {
logger.debug('No deployer key found in store for:', protocol);
const [wallet, encryptedKey] = await createTempDeployerWallet(protocol);
storeKey(protocol, encryptedKey);
tryPersistBrowserStorage();
wallets[protocol] = wallet;
}
} catch (error) {
throw new Error(`Error preparing temp deployer wallet for ${protocol}`, { cause: error });
}
}

return wallets;
}

async function createTempDeployerWallet(protocol: ProtocolType): Promise<[TypedWallet, string]> {
logger.info('Creating temp deployer wallet for:', protocol);
let wallet: TypedWallet;
let key: string;
if (protocol === ProtocolType.Ethereum) {
const entropy = utils.randomBytes(32);
key = utils.entropyToMnemonic(entropy);
wallet = { type: ProviderType.EthersV5, wallet: Wallet.fromMnemonic(key) };
} else {
throw new Error(`Unsupported protocol for temp deployer wallet: ${protocol}`);
}

const encryptedKey = await encryptString(
key,
config.tempWalletEncryptionKey,
config.tempWalletEncryptionSalt,
);
logger.info('Temp deployer wallet created for:', protocol);
return [wallet, encryptedKey];
}

async function getTempDeployerWallet(
protocol: ProtocolType,
encryptedKey: string,
): Promise<TypedWallet> {
logger.debug('Instantiating temp deployer wallet from key for:', protocol);
const key = await decryptString(
encryptedKey,
config.tempWalletEncryptionKey,
config.tempWalletEncryptionSalt,
);
if (protocol === ProtocolType.Ethereum) {
const wallet = Wallet.fromMnemonic(key);
return { type: ProviderType.EthersV5, wallet };
} else {
throw new Error(`Unsupported protocol for temp deployer wallet: ${protocol}`);
}
}

export function getDeployerAddressForProtocol(
wallets: TempDeployerWallets,
protocol: ProtocolType,
) {
const typedWallet = wallets[protocol];
if (!typedWallet) return undefined;
if (typedWallet.type === ProviderType.EthersV5) {
return typedWallet.wallet.address;
} else {
throw new Error(`Unsupported wallet type for address: ${typedWallet.type}`);
}
}

function tryPersistBrowserStorage() {
// Request persistent storage for site
// This prevents browser from clearing local storage when space runs low. Rare but possible.
// Not a critical perm (and not supported in safari) so not blocking on this
if (navigator?.storage?.persist) {
navigator.storage
.persist()
.then((isPersisted) => {
logger.debug(`Is persisted storage granted: ${isPersisted}`);
})
.catch((reason) => {
logger.error('Error enabling storage persist setting', reason);
});
}
}
35 changes: 0 additions & 35 deletions src/features/deployerWallet/manage.ts

This file was deleted.

84 changes: 0 additions & 84 deletions src/features/deployerWallet/storage.ts

This file was deleted.

Loading

0 comments on commit e97766c

Please sign in to comment.