Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: merge main into injective #431

Merged
merged 12 commits into from
Feb 28, 2025
Merged
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @jmrossy @nambrot
* @xaroz @cmcewen
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@headlessui/react": "^2.2.0",
"@hyperlane-xyz/registry": "7.2.2",
"@hyperlane-xyz/sdk": "8.5.0",
"@hyperlane-xyz/utils": "8.5.0",
"@hyperlane-xyz/widgets": "8.5.0",
"@hyperlane-xyz/registry": "10.5.0",
"@hyperlane-xyz/sdk": "8.7.0",
"@hyperlane-xyz/utils": "8.7.0",
"@hyperlane-xyz/widgets": "8.7.0",
"@interchain-ui/react": "^1.23.28",
"@metamask/post-message-stream": "6.1.2",
"@metamask/providers": "10.2.1",
Expand Down
2 changes: 2 additions & 0 deletions src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MultiProtocolWalletModal } from '@hyperlane-xyz/widgets';
import Head from 'next/head';
import { PropsWithChildren } from 'react';
import { APP_NAME } from '../../consts/app';
import { config } from '../../consts/config';
import { useStore } from '../../features/store';
import { SideBarMenu } from '../../features/wallet/SideBarMenu';
import { Footer } from '../nav/Footer';
Expand Down Expand Up @@ -38,6 +39,7 @@ export function AppLayout({ children }: PropsWithChildren) {
<MultiProtocolWalletModal
isOpen={showEnvSelectModal}
close={() => setShowEnvSelectModal(false)}
protocols={config.walletProtocols}
/>
<SideBarMenu
onClose={() => setIsSideBarOpen(false)}
Expand Down
57 changes: 57 additions & 0 deletions src/components/nav/FloatingButtonStrip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import Link from 'next/link';

import { DocsIcon, HistoryIcon, IconButton, PlusIcon, useModal } from '@hyperlane-xyz/widgets';
import { config } from '../../consts/config';
import { links } from '../../consts/links';
import { useStore } from '../../features/store';
import { AddWarpConfigModal } from '../../features/warpCore/AddWarpConfigModal';
import { Color } from '../../styles/Color';

export function FloatingButtonStrip() {
const { setIsSideBarOpen, isSideBarOpen } = useStore((s) => ({
setIsSideBarOpen: s.setIsSideBarOpen,
isSideBarOpen: s.isSideBarOpen,
}));

const {
isOpen: isAddWarpConfigOpen,
open: openAddWarpConfig,
close: closeAddWarpConfig,
} = useModal();

return (
<>
<div className="absolute -right-8 top-2 hidden flex-col items-center justify-end gap-3 sm:flex">
<IconButton
className={`p-0.5 ${styles.roundedCircle}`}
title="History"
onClick={() => setIsSideBarOpen(!isSideBarOpen)}
>
<HistoryIcon color={Color.primary['500']} height={22} width={22} />
</IconButton>
{config.showAddRouteButton && (
<IconButton
className={styles.roundedCircle}
title="Add route"
onClick={openAddWarpConfig}
>
<PlusIcon color={Color.primary['500']} height={26} width={26} />
</IconButton>
)}
<Link
href={links.warpDocs}
target="_blank"
className={`p-0.5 ${styles.roundedCircle} ${styles.link}`}
>
<DocsIcon color={Color.primary['500']} height={21} width={21} className="p-px" />
</Link>
</div>
<AddWarpConfigModal isOpen={isAddWarpConfigOpen} close={closeAddWarpConfig} />
</>
);
}

const styles = {
link: 'hover:opacity-70 active:opacity-60',
roundedCircle: 'rounded-full bg-white',
};
7 changes: 7 additions & 0 deletions src/consts/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ChainMap } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { ADDRESS_BLACKLIST } from './blacklist';

const isDevMode = process?.env?.NODE_ENV === 'development';
Expand All @@ -13,29 +14,35 @@ const chainWalletWhitelists = JSON.parse(process?.env?.NEXT_PUBLIC_CHAIN_WALLET_
interface Config {
addressBlacklist: string[]; // A list of addresses that are blacklisted and cannot be used in the app
chainWalletWhitelists: ChainMap<string[]>; // A map of chain names to a list of wallet names that work for it
defaultOriginChain: string | undefined; // The initial origin chain to show when app first loads
enableExplorerLink: boolean; // Include a link to the hyperlane explorer in the transfer modal
isDevMode: boolean; // Enables some debug features in the app
registryUrl: string | undefined; // Optional URL to use a custom registry instead of the published canonical version
registryBranch?: string | undefined; // Optional customization of the registry branch instead of main
registryProxyUrl?: string; // Optional URL to use a custom proxy for the GithubRegistry
showAddRouteButton: boolean; // Show/Hide the add route config icon in the button strip
showDisabledTokens: boolean; // Show/Hide invalid token options in the selection modal
showTipBox: boolean; // Show/Hide the blue tip box above the transfer form
transferBlacklist: string; // comma-separated list of routes between which transfers are disabled. Expects Caip2Id-Caip2Id (e.g. ethereum:1-sealevel:1399811149)
version: string; // Matches version number in package.json
walletConnectProjectId: string; // Project ID provided by walletconnect
walletProtocols: ProtocolType[] | undefined; // Wallet Protocols to show in the wallet connect modal. Leave undefined to include all of them
}

export const config: Config = Object.freeze({
addressBlacklist: ADDRESS_BLACKLIST.map((address) => address.toLowerCase()),
chainWalletWhitelists,
enableExplorerLink: false,
defaultOriginChain: undefined,
isDevMode,
registryUrl,
registryBranch,
registryProxyUrl,
showAddRouteButton: false,
showDisabledTokens: false,
showTipBox: false,
version,
transferBlacklist,
walletConnectProjectId,
walletProtocols: undefined,
});
57 changes: 41 additions & 16 deletions src/features/store.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { GithubRegistry, IRegistry } from '@hyperlane-xyz/registry';
import { ChainMap, ChainMetadata, MultiProtocolProvider, WarpCore } from '@hyperlane-xyz/sdk';
import {
ChainMap,
ChainMetadata,
MultiProtocolProvider,
WarpCore,
WarpCoreConfig,
} from '@hyperlane-xyz/sdk';
import { objFilter } from '@hyperlane-xyz/utils';
import { toast } from 'react-toastify';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { config } from '../consts/config';
import { logger } from '../utils/logger';
import { assembleChainMetadata } from './chains/metadata';
import { assembleWarpCoreConfig } from './tokens/warpCoreConfig';
import { FinalTransferStatuses, TransferContext, TransferStatus } from './transfer/types';
import { assembleWarpCoreConfig } from './warpCore/warpCoreConfig';

// Increment this when persist state has breaking changes
const PERSIST_STATE_VERSION = 2;
Expand All @@ -18,8 +24,12 @@ const PERSIST_STATE_VERSION = 2;
export interface AppState {
// Chains and providers
chainMetadata: ChainMap<ChainMetadata>;
// Overrides to chain metadata set by user via the chain picker
chainMetadataOverrides: ChainMap<Partial<ChainMetadata>>;
setChainMetadataOverrides: (overrides?: ChainMap<Partial<ChainMetadata> | undefined>) => void;
// Overrides to warp core configs added by user
warpCoreConfigOverrides: WarpCoreConfig[];
setWarpCoreConfigOverrides: (overrides?: WarpCoreConfig[] | undefined) => void;
multiProvider: MultiProtocolProvider;
registry: IRegistry;
warpCore: WarpCore;
Expand Down Expand Up @@ -61,9 +71,21 @@ export const useStore = create<AppState>()(
overrides: ChainMap<Partial<ChainMetadata> | undefined> = {},
) => {
logger.debug('Setting chain overrides in store');
const { multiProvider } = await initWarpContext(get().registry, overrides);
const { multiProvider, warpCore } = await initWarpContext({
...get(),
chainMetadataOverrides: overrides,
});
const filtered = objFilter(overrides, (_, metadata) => !!metadata);
set({ chainMetadataOverrides: filtered, multiProvider });
set({ chainMetadataOverrides: filtered, multiProvider, warpCore });
},
warpCoreConfigOverrides: [],
setWarpCoreConfigOverrides: async (overrides: WarpCoreConfig[] | undefined = []) => {
logger.debug('Setting warp core config overrides in store');
const { multiProvider, warpCore } = await initWarpContext({
...get(),
warpCoreConfigOverrides: overrides,
});
set({ warpCoreConfigOverrides: overrides, multiProvider, warpCore });
},
multiProvider: new MultiProtocolProvider({}),
registry: new GithubRegistry({
Expand Down Expand Up @@ -137,31 +159,34 @@ export const useStore = create<AppState>()(
logger.error('Error during hydration', error);
return;
}
initWarpContext(state.registry, state.chainMetadataOverrides).then(
({ registry, chainMetadata, multiProvider, warpCore }) => {
state.setWarpContext({ registry, chainMetadata, multiProvider, warpCore });
logger.debug('Rehydration complete');
},
);
initWarpContext(state).then(({ registry, chainMetadata, multiProvider, warpCore }) => {
state.setWarpContext({ registry, chainMetadata, multiProvider, warpCore });
logger.debug('Rehydration complete');
});
};
},
},
),
);

async function initWarpContext(
registry: IRegistry,
storeMetadataOverrides: ChainMap<Partial<ChainMetadata> | undefined>,
) {
async function initWarpContext({
registry,
chainMetadataOverrides,
warpCoreConfigOverrides,
}: {
registry: IRegistry;
chainMetadataOverrides: ChainMap<Partial<ChainMetadata> | undefined>;
warpCoreConfigOverrides: WarpCoreConfig[];
}) {
try {
const coreConfig = await assembleWarpCoreConfig();
const coreConfig = await assembleWarpCoreConfig(warpCoreConfigOverrides);
const chainsInTokens = Array.from(new Set(coreConfig.tokens.map((t) => t.chainName)));
// Pre-load registry content to avoid repeated requests
await registry.listRegistryContent();
const { chainMetadata, chainMetadataWithOverrides } = await assembleChainMetadata(
chainsInTokens,
registry,
storeMetadataOverrides,
chainMetadataOverrides,
);
const multiProvider = new MultiProtocolProvider(chainMetadataWithOverrides);
const warpCore = WarpCore.FromConfig(multiProvider, coreConfig);
Expand Down
2 changes: 1 addition & 1 deletion src/features/tokens/TokenListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export function TokenList({
</div>
<div className="ml-2 min-w-0 shrink text-left">
<div className="w-full truncate text-xs">
{t.token.addressOrDenom || 'Native chain token'}
{t.token.collateralAddressOrDenom || t.token.addressOrDenom || 'Native chain token'}
</div>
<div className="mt-0.5 flex space-x-1 text-xs">
<span>{`Decimals: ${t.token.decimals}`}</span>
Expand Down
25 changes: 25 additions & 0 deletions src/features/tokens/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { isValidAddress } from '@hyperlane-xyz/utils';
import { useAccountAddressForChain } from '@hyperlane-xyz/widgets';
import { useQuery } from '@tanstack/react-query';
import { toast } from 'react-toastify';
import { Hex } from 'viem';
import { useBalance as useWagmiBalance } from 'wagmi';
import { useToastError } from '../../components/toast/useToastError';
import { logger } from '../../utils/logger';
import { useMultiProvider } from '../chains/hooks';
Expand Down Expand Up @@ -61,3 +63,26 @@ export async function getDestinationNativeBalance(
return undefined;
}
}

export function useEvmWalletBalance(
chainName: string,
chainId: number,
token: string,
refetchEnabled: boolean,
) {
const multiProvider = useMultiProvider();
const address = useAccountAddressForChain(multiProvider, chainName);
const allowRefetch = Boolean(address) && refetchEnabled;

const { data, isError, isLoading } = useWagmiBalance({
address: address ? (address as Hex) : undefined,
token: token ? (token as Hex) : undefined,
chainId: chainId,
query: {
refetchInterval: allowRefetch ? 5000 : false,
enabled: allowRefetch,
},
});

return { balance: data, isError, isLoading };
}
13 changes: 11 additions & 2 deletions src/features/tokens/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function tryFindToken(
}
}

function getTokenIndexFromChains(
export function getTokenIndexFromChains(
warpCore: WarpCore,
addressOrDenom: string | null,
origin: string,
Expand All @@ -68,8 +68,9 @@ export function getInitialTokenIndex(
addressOrDenom: string | null,
originQuery?: string,
destinationQuery?: string,
defaultOriginToken?: Token,
): number | undefined {
const firstToken = warpCore.tokens[0];
const firstToken = defaultOriginToken || warpCore.tokens[0];
const connectedToken = firstToken.connections?.[0];

// origin query and destination query is defined
Expand All @@ -87,3 +88,11 @@ export function getInitialTokenIndex(

return undefined;
}

export function tryFindTokenConnection(token: Token, chainName: string) {
const connectedToken = token.connections?.find(
(connection) => connection.token.chainName === chainName,
);

return connectedToken ? connectedToken.token : null;
}
57 changes: 0 additions & 57 deletions src/features/tokens/warpCoreConfig.ts

This file was deleted.

Loading