Merge pull request #13 from hyperlane-xyz/permisionless-warp-deploy

Support Permisionless warp deployments
pull/14/head
J M Rossy 2 years ago committed by GitHub
commit 07208401d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      CUSTOMIZE.md
  2. 4
      package.json
  3. 5
      src/components/icons/ChainIcon.tsx
  4. 4
      src/components/toast/TxSuccessToast.tsx
  5. 23
      src/consts/chains.json
  6. 13
      src/consts/environments.ts
  7. 10
      src/features/chains/ChainSelectField.tsx
  8. 102
      src/features/chains/ChainSelectModal.tsx
  9. 68
      src/features/chains/metadata.ts
  10. 18
      src/features/providers.ts
  11. 23
      src/features/tokens/routes.ts
  12. 68
      src/features/transfer/TransferTokenForm.tsx
  13. 5
      src/pages/_app.tsx
  14. 25
      src/utils/chains.ts
  15. 19
      src/utils/explorers.ts
  16. 48
      yarn.lock

@ -13,6 +13,14 @@ The list should use the [Uniswap Token List](https://tokenlists.org) standard wi
By default, the app uses public RPC providers based on the Hyperlane SDK's default settings.
This can be changed in '`./src/features/providers.ts`.
## Permisionless Chain Support
_This section is only relevant if you want to include chains not already supported by Hyperlane._
By default, the app will use only the chains that are included in the Hyperlane SDK and connected to the tokens you specify in the token list (see above).
To add support for additional chains, fill in the required chain metadata in `./src/consts/chains.json`. You can also use this file to override any default chain configs set in the SDK. It uses the same schema as the `warp-route-chain-config.json` config used to deploy Warp Routes from the token repo, with one exception: the chains config here expects a `logoImgSrc` field.
## Tip Card Content
The content of the tip card above the form can be customized in `./src/components/tip/TipCard.tsx`

@ -5,8 +5,8 @@
"author": "J M Rossy",
"dependencies": {
"@headlessui/react": "^1.7.7",
"@hyperlane-xyz/hyperlane-token": "^1.0.0",
"@hyperlane-xyz/sdk": "^1.0.0",
"@hyperlane-xyz/hyperlane-token": "^1.0.1-beta0",
"@hyperlane-xyz/sdk": "^1.0.1-beta0",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@rainbow-me/rainbowkit": "^0.8.1",
"@tanstack/react-query": "^4.20.4",

@ -19,8 +19,8 @@ import MoonbeamColor from '@hyperlane-xyz/sdk/logos/color/moonbeam.svg';
import OptimismColor from '@hyperlane-xyz/sdk/logos/color/optimism.svg';
import PolygonColor from '@hyperlane-xyz/sdk/logos/color/polygon.svg';
import { chainIdToCustomConfig, getChainDisplayName } from '../../features/chains/metadata';
import QuestionMark from '../../images/icons/question-mark.svg';
import { getChainDisplayName } from '../../utils/chains';
// Keep up to date as new chains are added or
// icon will fallback to default
@ -71,7 +71,8 @@ interface Props {
function _ChainIcon({ chainId, size = 32, color = true, background = false }: Props) {
const iconSet = color ? CHAIN_TO_COLOR_ICON : CHAIN_TO_MONOCHROME_ICON;
const imageSrc = (chainId && iconSet[chainId]) || QuestionMark;
const imageSrc =
(chainId && (iconSet[chainId] || chainIdToCustomConfig[chainId]?.logoImgSrc)) || QuestionMark;
if (background) {
return (

@ -1,6 +1,6 @@
import { toast } from 'react-toastify';
import { chainIdToMetadata } from '@hyperlane-xyz/sdk';
import { getChainExplorerUrl } from '../../features/chains/metadata';
export function toastTxSuccess(msg: string, txHash: string, chainId: number) {
toast.success(<TxSuccessToast msg={msg} txHash={txHash} chainId={chainId} />, {
@ -17,7 +17,7 @@ export function TxSuccessToast({
txHash: string;
chainId: number;
}) {
const explorerBaseUrl = chainIdToMetadata[chainId]?.blockExplorers[0].url;
const explorerBaseUrl = getChainExplorerUrl(chainId);
const url = explorerBaseUrl ? `${explorerBaseUrl}/tx/${txHash}` : '';
return (
<div>

@ -0,0 +1,23 @@
{
"mycustomchain": {
"id": 1234,
"name": "mycustomchain",
"displayName": "My Chain",
"nativeToken": { "name": "Ether", "symbol": "ETH", "decimals": 18 },
"publicRpcUrls": [{ "http": "https://mycustomchain-rpc.com" }],
"blockExplorers": [
{
"name": "MyCustomScan",
"url": "https://mycustomchain-scan.com",
"apiUrl": "https://api.mycustomchain-scan.com",
"family": "etherscan"
}
],
"blocks": {
"confirmations": 1,
"reorgPeriod": 1,
"estimateBlockTime": 10
},
"logoImgSrc": "/logo.svg"
}
}

@ -1,13 +0,0 @@
export enum Environment {
Mainnet = 'mainnet',
Testnet2 = 'testnet2',
Testnet3 = 'testnet3',
}
export const environments = Object.values(Environment);
export const envDisplayValue = {
[Environment.Mainnet]: 'Mainnet',
[Environment.Testnet2]: 'Testnet',
[Environment.Testnet3]: 'Testnet',
};

@ -4,18 +4,19 @@ import { useState } from 'react';
import { ChainIcon } from '../../components/icons/ChainIcon';
import ChevronIcon from '../../images/icons/chevron-down.svg';
import { getChainDisplayName } from '../../utils/chains';
import { ChainSelectModal } from './ChainSelectModal';
import { ChainSelectListModal } from './ChainSelectModal';
import { getChainDisplayName } from './metadata';
type Props = {
name: string;
label: string;
chainIds: number[];
onChange?: (chainId: number) => void;
disabled?: boolean;
};
export function ChainSelectField({ name, label, onChange, disabled }: Props) {
export function ChainSelectField({ name, label, chainIds, onChange, disabled }: Props) {
const [field, , helpers] = useField<number>(name);
const handleChange = (newChainId: number) => {
@ -51,9 +52,10 @@ export function ChainSelectField({ name, label, onChange, disabled }: Props) {
</div>
<Image src={ChevronIcon} width={12} height={8} alt="" />
</button>
<ChainSelectModal
<ChainSelectListModal
isOpen={isModalOpen}
close={() => setIsModalOpen(false)}
chainIds={chainIds}
onSelect={handleChange}
/>
</div>

@ -1,16 +1,17 @@
import { mainnetChainsMetadata, testnetChainsMetadata } from '@hyperlane-xyz/sdk';
import { ChainIcon } from '../../components/icons/ChainIcon';
import { Modal } from '../../components/layout/Modal';
import { getChainDisplayName } from '../../utils/chains';
export function ChainSelectModal({
import { getChainDisplayName, getChainMetadata } from './metadata';
export function ChainSelectListModal({
isOpen,
close,
chainIds,
onSelect,
}: {
isOpen: boolean;
close: () => void;
chainIds: number[];
onSelect: (chainId: number) => void;
}) {
const onSelectChain = (chainId: number) => {
@ -20,36 +21,73 @@ export function ChainSelectModal({
};
};
const chainMetadata = chainIds.map((c) => getChainMetadata(c));
return (
<Modal isOpen={isOpen} title="Send Tokens" close={close}>
<div className="mt-1 flex justify-between">
<div className="flex flex-col space-y-0.5 relative -left-2">
<h4 className="py-1.5 px-2 text-sm text-gray-500 uppercase">Mainnet</h4>
{mainnetChainsMetadata.map((c) => (
<button
key={c.name}
className="py-1.5 px-2 text-sm flex items-center rounded hover:bg-gray-100 active:bg-gray-200 transition-all duration-200"
onClick={onSelectChain(c.id)}
>
<ChainIcon chainId={c.id} size={16} background={false} />
<span className="ml-2">{getChainDisplayName(c.id, true)}</span>
</button>
))}
</div>
<div className="flex flex-col space-y-0.5 pr-3">
<h4 className="py-1.5 px-2 text-sm text-gray-500 uppercase">Testnet</h4>
{testnetChainsMetadata.map((c) => (
<button
key={c.name}
className="py-1.5 px-2 text-sm flex items-center rounded hover:bg-gray-100 active:bg-gray-200 transition-all duration-200"
onClick={onSelectChain(c.id)}
>
<ChainIcon chainId={c.id} size={16} background={false} />
<span className="ml-2">{getChainDisplayName(c.id, true)}</span>
</button>
))}
</div>
<Modal isOpen={isOpen} title="Select Chain" close={close}>
<div className="mt-2 flex flex-col space-y-1">
{chainMetadata.map((c) => (
<button
key={c.name}
className="py-1.5 px-2 text-sm flex items-center rounded hover:bg-gray-100 active:bg-gray-200 transition-all duration-200"
onClick={onSelectChain(c.id)}
>
<ChainIcon chainId={c.id} size={16} background={false} />
<span className="ml-2">{getChainDisplayName(c.id, true)}</span>
</button>
))}
</div>
</Modal>
);
}
// TODO update to support dynamic chain lists
// export function ChainSelectGridModal({
// isOpen,
// close,
// onSelect,
// }: {
// isOpen: boolean;
// close: () => void;
// onSelect: (chainId: number) => void;
// }) {
// const onSelectChain = (chainId: number) => {
// return () => {
// onSelect(chainId);
// close();
// };
// };
// return (
// <Modal isOpen={isOpen} title="elect Chain" close={close}>
// <div className="mt-1 flex justify-between">
// <div className="flex flex-col space-y-0.5 relative -left-2">
// <h4 className="py-1.5 px-2 text-sm text-gray-500 uppercase">Mainnet</h4>
// {mainnetChainsMetadata.map((c) => (
// <button
// key={c.name}
// className="py-1.5 px-2 text-sm flex items-center rounded hover:bg-gray-100 active:bg-gray-200 transition-all duration-200"
// onClick={onSelectChain(c.id)}
// >
// <ChainIcon chainId={c.id} size={16} background={false} />
// <span className="ml-2">{getChainDisplayName(c.id, true)}</span>
// </button>
// ))}
// </div>
// <div className="flex flex-col space-y-0.5 pr-3">
// <h4 className="py-1.5 px-2 text-sm text-gray-500 uppercase">Testnet</h4>
// {testnetChainsMetadata.map((c) => (
// <button
// key={c.name}
// className="py-1.5 px-2 text-sm flex items-center rounded hover:bg-gray-100 active:bg-gray-200 transition-all duration-200"
// onClick={onSelectChain(c.id)}
// >
// <ChainIcon chainId={c.id} size={16} background={false} />
// <span className="ml-2">{getChainDisplayName(c.id, true)}</span>
// </button>
// ))}
// </div>
// </div>
// </Modal>
// );
// }

@ -0,0 +1,68 @@
import type { Chain as WagmiChain } from '@wagmi/chains';
import { ChainMetadata, chainIdToMetadata, objMap, wagmiChainMetadata } from '@hyperlane-xyz/sdk';
import CustomChainConfig from '../../consts/chains.json';
export type CustomChainMetadata = Omit<ChainMetadata, 'name'> & {
name: string;
logoImgSrc: string;
};
export const chainIdToCustomConfig = Object.values(CustomChainConfig).reduce<
Record<number, CustomChainMetadata>
>((result, config) => {
result[config.id] = config as CustomChainMetadata;
return result;
}, {});
export function getChainMetadata(chainId: number): ChainMetadata {
if (chainIdToCustomConfig[chainId]) return chainIdToCustomConfig[chainId] as ChainMetadata;
else if (chainIdToMetadata[chainId]) return chainIdToMetadata[chainId];
else throw new Error(`No metadata found for chain ${chainId}`);
}
export function getChainRpcUrl(chainId: number): string {
const metadata = getChainMetadata(chainId);
const first = metadata.publicRpcUrls[0];
return first.http;
}
export function getChainExplorerUrl(chainId: number, apiUrl = false): string {
const metadata = getChainMetadata(chainId);
const first = metadata.blockExplorers[0];
return apiUrl ? first.apiUrl || first.url : first.url;
}
export function getChainDisplayName(chainId?: number, shortName = false): string {
if (!chainId) return 'Unknown';
const metadata = getChainMetadata(chainId);
return shortName ? metadata.displayNameShort || metadata.displayName : metadata.displayName;
}
// Metadata formatted for use in Wagmi config
export function getWagmiChainConfig() {
return Object.values({
...wagmiChainMetadata,
...objMap(chainIdToCustomConfig as Record<string, ChainMetadata>, toWagmiConfig),
});
}
// TODO move to SDK
function toWagmiConfig(_: any, metadata: ChainMetadata): WagmiChain {
return {
id: metadata.id,
name: metadata.displayName,
network: metadata.name as string,
nativeCurrency: metadata.nativeToken,
rpcUrls: { default: { http: [metadata.publicRpcUrls[0].http] } },
blockExplorers: metadata.blockExplorers.length
? {
default: {
name: metadata.blockExplorers[0].name,
url: metadata.blockExplorers[0].url,
},
}
: undefined,
};
}

@ -1,9 +1,15 @@
import { chainConnectionConfigs, chainIdToMetadata } from '@hyperlane-xyz/sdk';
import { providers } from 'ethers';
// This uses public RPC URLs from the SDK but can be
// changed to use other providers as needed
import { getChainRpcUrl } from './chains/metadata';
const providerCache = {};
// This uses public RPC URLs from the chain configs in the SDK and/or custom settings
// Can be freely changed to use other providers/urls as needed
export function getProvider(chainId: number) {
const chainName = chainIdToMetadata[chainId]?.name;
if (!chainName) throw new Error(`No metadata found for chain: ${chainId}`);
return chainConnectionConfigs[chainName].provider;
if (providerCache[chainId]) return providerCache[chainId];
const rpcUrl = getChainRpcUrl(chainId);
const provider = new providers.JsonRpcProvider(rpcUrl, chainId);
providerCache[chainId] = provider;
return provider;
}

@ -1,6 +1,6 @@
import { useQuery } from '@tanstack/react-query';
import { useMemo } from 'react';
import { chainIdToMetadata } from '@hyperlane-xyz/sdk';
import { utils } from '@hyperlane-xyz/utils';
import { areAddressesEqual, isValidAddress, normalizeAddress } from '../../utils/addresses';
@ -33,7 +33,7 @@ function computeTokenRoutes(tokens: ListedTokenWithHypTokens[]) {
const tokenRoutes: RoutesMap = {};
// Instantiate map structure
const allChainIds = Object.keys(chainIdToMetadata);
const allChainIds = getChainsFromTokens(tokens);
for (const source of allChainIds) {
tokenRoutes[source] = {};
for (const dest of allChainIds) {
@ -88,6 +88,17 @@ function computeTokenRoutes(tokens: ListedTokenWithHypTokens[]) {
return tokenRoutes;
}
function getChainsFromTokens(tokens: ListedTokenWithHypTokens[]) {
const chains = new Set<number>();
for (const token of tokens) {
chains.add(token.chainId);
for (const remoteToken of token.hypTokens) {
chains.add(remoteToken.chainId);
}
}
return Array.from(chains);
}
export function getTokenRoutes(
sourceChainId: number,
destinationChainId: number,
@ -140,8 +151,8 @@ export function useTokenRoutes() {
// TODO parallelization here would be good, either with RPC batching or just promise.all, but
// avoiding it for now due to limitations of public RPC providers
for (const chainId of domains) {
const hypTokenBytes = await collateralContract.routers(chainId);
const hypTokenAddr = utils.bytes32ToAddress(hypTokenBytes);
const hypTokenAddrBytes = await collateralContract.routers(chainId);
const hypTokenAddr = utils.bytes32ToAddress(hypTokenAddrBytes);
hypTokens.push({ chainId, address: normalizeAddress(hypTokenAddr) });
}
tokens.push({ ...token, hypTokens });
@ -154,3 +165,7 @@ export function useTokenRoutes() {
return { isLoading, hasError, tokenRoutes };
}
export function useRouteChains(tokenRoutes: RoutesMap): number[] {
return useMemo(() => Object.keys(tokenRoutes).map((chainId) => parseInt(chainId)), [tokenRoutes]);
}

@ -1,10 +1,8 @@
import { useQueryClient } from '@tanstack/react-query';
import { Form, Formik, useFormikContext } from 'formik';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useAccount } from 'wagmi';
import { chainIdToMetadata, chainMetadata } from '@hyperlane-xyz/sdk';
import { ConnectAwareSubmitButton } from '../../components/buttons/ConnectAwareSubmitButton';
import { IconButton } from '../../components/buttons/IconButton';
import { SolidButton } from '../../components/buttons/SolidButton';
@ -16,26 +14,30 @@ import SwapIcon from '../../images/icons/swap.svg';
import { Color } from '../../styles/Color';
import { isValidAddress } from '../../utils/addresses';
import { fromWeiRounded, toWei, tryParseAmount } from '../../utils/amount';
import { getChainDisplayName, getChainEnvironment } from '../../utils/chains';
import { logger } from '../../utils/logger';
import { ChainSelectField } from '../chains/ChainSelectField';
import { getChainDisplayName } from '../chains/metadata';
import { TokenSelectField } from '../tokens/TokenSelectField';
import { RouteType, RoutesMap, getTokenRoute } from '../tokens/routes';
import { RouteType, RoutesMap, getTokenRoute, useRouteChains } from '../tokens/routes';
import { getCachedTokenBalance, useAccountTokenBalance } from '../tokens/useTokenBalance';
import { TransferTransactionsModal } from './TransferTransactionsModal';
import { TransferFormValues } from './types';
import { useTokenTransfer } from './useTokenTransfer';
const initialValues: TransferFormValues = {
sourceChainId: chainMetadata.goerli.id,
destinationChainId: chainMetadata.alfajores.id,
amount: '',
tokenAddress: '',
recipientAddress: '',
};
export function TransferTokenForm({ tokenRoutes }: { tokenRoutes: RoutesMap }) {
const chainIds = useRouteChains(tokenRoutes);
const initialValues: TransferFormValues = useMemo(
() => ({
sourceChainId: chainIds[0],
destinationChainId: chainIds[1],
amount: '',
tokenAddress: '',
recipientAddress: '',
}),
[chainIds],
);
// Flag for if form is in input vs review mode
const [isReview, setIsReview] = useState(false);
@ -57,28 +59,12 @@ export function TransferTokenForm({ tokenRoutes }: { tokenRoutes: RoutesMap }) {
tokenAddress,
recipientAddress,
}: TransferFormValues) => {
// Check chains
if (!sourceChainId || !chainIdToMetadata[sourceChainId]) {
return { sourceChainId: 'Invalid source chain' };
}
if (!destinationChainId || !chainIdToMetadata[destinationChainId]) {
return { destinationChainId: 'Invalid destination chain' };
}
if (getChainEnvironment(sourceChainId) !== getChainEnvironment(destinationChainId)) {
return { destinationChainId: 'Invalid chain combination' };
}
// Check addresses
if (!isValidAddress(recipientAddress)) {
return { recipientAddress: 'Invalid recipient' };
}
if (!isValidAddress(tokenAddress)) {
return { tokenAddress: 'Invalid token' };
}
// Check amount
if (!sourceChainId) return { sourceChainId: 'Invalid source chain' };
if (!destinationChainId) return { destinationChainId: 'Invalid destination chain' };
if (!isValidAddress(recipientAddress)) return { recipientAddress: 'Invalid recipient' };
if (!isValidAddress(tokenAddress)) return { tokenAddress: 'Invalid token' };
const parsedAmount = tryParseAmount(amount);
if (!parsedAmount || parsedAmount.lte(0)) {
return { amount: 'Invalid amount' };
}
if (!parsedAmount || parsedAmount.lte(0)) return { amount: 'Invalid amount' };
const cachedBalance = getCachedTokenBalance(
queryClient,
sourceChainId,
@ -112,7 +98,12 @@ export function TransferTokenForm({ tokenRoutes }: { tokenRoutes: RoutesMap }) {
{({ values }) => (
<Form className="flex flex-col items-stretch w-full mt-2">
<div className="flex items-center justify-center space-x-7 sm:space-x-10">
<ChainSelectField name="sourceChainId" label="From" disabled={isReview} />
<ChainSelectField
name="sourceChainId"
label="From"
chainIds={chainIds}
disabled={isReview}
/>
<div className="flex flex-col items-center">
<div className="flex mb-6 sm:space-x-1.5">
<HyperlaneChevron
@ -127,7 +118,12 @@ export function TransferTokenForm({ tokenRoutes }: { tokenRoutes: RoutesMap }) {
</div>
<SwapChainsButton disabled={isReview} />
</div>
<ChainSelectField name="destinationChainId" label="To" disabled={isReview} />
<ChainSelectField
name="destinationChainId"
label="To"
chainIds={chainIds}
disabled={isReview}
/>
</div>
<div className="mt-3 flex justify-between space-x-4">
<div className="flex-1">

@ -18,16 +18,15 @@ import 'react-toastify/dist/ReactToastify.css';
import { WagmiConfig, configureChains, createClient as createWagmiClient } from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { wagmiChainMetadata } from '@hyperlane-xyz/sdk';
import { ErrorBoundary } from '../components/errors/ErrorBoundary';
import { AppLayout } from '../components/layout/AppLayout';
import { getWagmiChainConfig } from '../features/chains/metadata';
import { Color } from '../styles/Color';
import '../styles/fonts.css';
import '../styles/globals.css';
import { useIsSsr } from '../utils/ssr';
const { chains, provider } = configureChains(Object.values(wagmiChainMetadata), [publicProvider()]);
const { chains, provider } = configureChains(getWagmiChainConfig(), [publicProvider()]);
const connectorConfig = {
appName: 'Hyperlane Warp Template',

@ -1,25 +0,0 @@
import { ChainName, Chains, Mainnets, chainIdToMetadata } from '@hyperlane-xyz/sdk';
import { Environment } from '../consts/environments';
import { logger } from './logger';
export function getChainDisplayName(chainId?: number, shortName = false) {
if (!chainId || !chainIdToMetadata[chainId]) return 'Unknown';
const metadata = chainIdToMetadata[chainId];
return shortName ? metadata.displayNameShort || metadata.displayName : metadata.displayName;
}
export function getChainEnvironment(chain: number | string) {
let chainName: ChainName;
if (typeof chain === 'number' && chainIdToMetadata[chain]) {
chainName = chainIdToMetadata[chain].name;
} else if (typeof chain === 'string' && Object.keys(Chains).includes(chain)) {
chainName = chain as ChainName;
} else {
logger.error(`Cannot get environment for invalid chain ${chain}`);
return Environment.Mainnet;
}
return Mainnets.includes(chainName) ? Environment.Mainnet : Environment.Testnet3;
}

@ -1,8 +1,7 @@
import { BigNumber, providers } from 'ethers';
import { chainIdToMetadata } from '@hyperlane-xyz/sdk';
import { config } from '../consts/config';
import { getChainExplorerUrl } from '../features/chains/metadata';
import { logger } from './logger';
import { retryAsync } from './retry';
@ -14,26 +13,14 @@ export interface ExplorerQueryResponse<R> {
result: R;
}
export function getExplorerUrl(chainId: number) {
const chain = chainIdToMetadata[chainId];
if (!chain?.blockExplorers?.length) return null;
return chain.blockExplorers[0].url;
}
export function getExplorerApiUrl(chainId: number) {
const chain = chainIdToMetadata[chainId];
if (!chain?.blockExplorers?.length) return null;
return chain.blockExplorers[0].apiUrl || chain.blockExplorers[0].url;
}
export function getTxExplorerUrl(chainId: number, hash?: string) {
const baseUrl = getExplorerUrl(chainId);
const baseUrl = getChainExplorerUrl(chainId);
if (!hash || !baseUrl) return null;
return `${baseUrl}/tx/${hash}`;
}
export async function queryExplorer<P>(chainId: number, path: string, useKey = true) {
const baseUrl = getExplorerApiUrl(chainId);
const baseUrl = getChainExplorerUrl(chainId, true);
if (!baseUrl) throw new Error(`No URL found for explorer for chain ${chainId}`);
let url = `${baseUrl}/${path}`;

@ -983,52 +983,52 @@ __metadata:
languageName: node
linkType: hard
"@hyperlane-xyz/core@npm:1.0.0":
version: 1.0.0
resolution: "@hyperlane-xyz/core@npm:1.0.0"
"@hyperlane-xyz/core@npm:1.0.1-beta0":
version: 1.0.1-beta0
resolution: "@hyperlane-xyz/core@npm:1.0.1-beta0"
dependencies:
"@hyperlane-xyz/utils": 1.0.0
"@hyperlane-xyz/utils": 1.0.1-beta0
"@openzeppelin/contracts": ^4.8.0
"@openzeppelin/contracts-upgradeable": ^4.8.0
checksum: 4a2bfcd87b3bdb868051ea24378db0d70449266ac9601e75da2842b8f0887e6715b6d42fdcef6bf604a2524c4e43bfdb90c1b41deb767a31afed18e8804c46b7
checksum: c2940686c462a2780f84f27d3e34c7788b542ac4792c36e2f88d433dd6f6dc2830da9ac5c6f07bf99969e09b815a9b562d235895cd7ccd42fa4e02dcaa00b76a
languageName: node
linkType: hard
"@hyperlane-xyz/hyperlane-token@npm:^1.0.0":
version: 1.0.0
resolution: "@hyperlane-xyz/hyperlane-token@npm:1.0.0"
"@hyperlane-xyz/hyperlane-token@npm:^1.0.1-beta0":
version: 1.0.1-beta0
resolution: "@hyperlane-xyz/hyperlane-token@npm:1.0.1-beta0"
dependencies:
"@hyperlane-xyz/core": 1.0.0
"@hyperlane-xyz/sdk": 1.0.0
"@hyperlane-xyz/utils": 1.0.0
"@hyperlane-xyz/core": 1.0.1-beta0
"@hyperlane-xyz/sdk": 1.0.1-beta0
"@hyperlane-xyz/utils": 1.0.1-beta0
"@openzeppelin/contracts-upgradeable": ^4.8.0
ethers: ^5.7.2
checksum: 59dc3bd87be61bb2a2295b766c7ba8c73e62e9f39acf2cb313a5dd54b306bf46e14ccf4c611259f7a578d3b2e58c7a3ce63bdd7210aa044947cf4791bc2aa5e2
checksum: 75252bf83bacaaab895e6f74ade8fb002c83c53fc182d61d1d39478da5c86aef544e18c16287046c77cd8b119ca3bec0b93dca9b6abdaecbe5173df223641c3f
languageName: node
linkType: hard
"@hyperlane-xyz/sdk@npm:1.0.0, @hyperlane-xyz/sdk@npm:^1.0.0":
version: 1.0.0
resolution: "@hyperlane-xyz/sdk@npm:1.0.0"
"@hyperlane-xyz/sdk@npm:1.0.1-beta0, @hyperlane-xyz/sdk@npm:^1.0.1-beta0":
version: 1.0.1-beta0
resolution: "@hyperlane-xyz/sdk@npm:1.0.1-beta0"
dependencies:
"@hyperlane-xyz/celo-ethers-provider": ^0.1.1
"@hyperlane-xyz/core": 1.0.0
"@hyperlane-xyz/utils": 1.0.0
"@hyperlane-xyz/core": 1.0.1-beta0
"@hyperlane-xyz/utils": 1.0.1-beta0
"@wagmi/chains": ^0.1.3
coingecko-api: ^1.0.10
cross-fetch: ^3.1.5
debug: ^4.3.4
ethers: ^5.7.2
checksum: 93c878ba85fde204d9d4c6e19c6d467a7dd232253cb5b03e7a5b52a6121962c1a6592b9431e6af92242b763c38eac45978ddec9dfd5d6bd4557fb565a14cd81b
checksum: aa85ced1fc4f0ab57d0373ce3c8a59a175573064f50cb20a4d6b7fe61a070c381e222c735a8872880d760bd960be24644acadf579e7adee3c00a19ba3fb56946
languageName: node
linkType: hard
"@hyperlane-xyz/utils@npm:1.0.0":
version: 1.0.0
resolution: "@hyperlane-xyz/utils@npm:1.0.0"
"@hyperlane-xyz/utils@npm:1.0.1-beta0":
version: 1.0.1-beta0
resolution: "@hyperlane-xyz/utils@npm:1.0.1-beta0"
dependencies:
ethers: ^5.7.2
checksum: f19440f507d8219e62b5792211459233c478ee72daa5109a502bd66eb611bdc09e55c5b67f32f3b46a89307c7f70e672acaf58a4018adb4f343a5eb566cd1dcd
checksum: a87ac6cd90673bbfd5b17dbb68f8fb7626ad16af63315e1080817a87d6c8dfa58d10c69720a5840bf5926942072fa1b9b696ffd4325c007a30bef46cd048402b
languageName: node
linkType: hard
@ -1037,8 +1037,8 @@ __metadata:
resolution: "@hyperlane-xyz/warp-ui-template@workspace:."
dependencies:
"@headlessui/react": ^1.7.7
"@hyperlane-xyz/hyperlane-token": ^1.0.0
"@hyperlane-xyz/sdk": ^1.0.0
"@hyperlane-xyz/hyperlane-token": ^1.0.1-beta0
"@hyperlane-xyz/sdk": ^1.0.1-beta0
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6"
"@rainbow-me/rainbowkit": ^0.8.1
"@tanstack/react-query": ^4.20.4

Loading…
Cancel
Save