Skip to content

Commit

Permalink
Merge pull request #405 from hyperlane-xyz/main-to-trump
Browse files Browse the repository at this point in the history
Main to trump
  • Loading branch information
Xaroz authored Jan 28, 2025
2 parents 7cfc050 + 024b770 commit 7663654
Show file tree
Hide file tree
Showing 15 changed files with 2,057 additions and 939 deletions.
14 changes: 8 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hyperlane-xyz/warp-ui-template",
"description": "A web app template for building Hyperlane Warp Route UIs",
"version": "7.1.0",
"version": "8.5.0",
"author": "J M Rossy",
"dependencies": {
"@chakra-ui/next-js": "^2.4.2",
Expand All @@ -17,10 +17,10 @@
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@headlessui/react": "^2.2.0",
"@hyperlane-xyz/registry": "6.19.1",
"@hyperlane-xyz/sdk": "7.1.0",
"@hyperlane-xyz/utils": "7.1.0",
"@hyperlane-xyz/widgets": "7.1.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",
"@interchain-ui/react": "^1.23.28",
"@metamask/post-message-stream": "6.1.2",
"@metamask/providers": "10.2.1",
Expand Down Expand Up @@ -69,6 +69,8 @@
"tailwindcss": "^3.4.15",
"ts-node": "^10.9.2",
"typescript": "5.6.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.0.3",
"yaml": "^2.6.0",
"yaml-loader": "^0.8.1"
},
Expand All @@ -88,7 +90,7 @@
"typecheck": "tsc",
"lint": "next lint",
"start": "next start",
"test": "echo 'No tests'",
"test": "vitest --watch false",
"prettier": "prettier --write ./src"
},
"types": "dist/src/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/components/buttons/ConnectAwareSubmitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export function ConnectAwareSubmitButton<FormValues = any>({ chainName, text, cl
useTimeout(clearErrors, 3500);

return (
<SolidButton type={type} color={color} onClick={onClick} classes={classes}>
<SolidButton type={type} color={color} onClick={onClick} className={classes}>
{content}
</SolidButton>
);
Expand Down
6 changes: 3 additions & 3 deletions src/components/buttons/SolidButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface ButtonProps {
type?: 'submit' | 'reset' | 'button';
color?: 'white' | 'primary' | 'accent' | 'green' | 'red' | 'gray'; // defaults to primary
bold?: boolean;
classes?: string;
className?: string;
icon?: ReactElement;
}

Expand All @@ -15,7 +15,7 @@ export function SolidButton(
type,
onClick,
color: _color,
classes,
className,
bold,
icon,
disabled,
Expand Down Expand Up @@ -48,7 +48,7 @@ export function SolidButton(
}
const onDisabled = 'disabled:bg-gray-300 disabled:text-gray-500';
const weight = bold ? 'font-semibold' : '';
const allClasses = `${base} ${baseColors} ${onHover} ${onDisabled} ${weight} ${classes}`;
const allClasses = `${base} ${baseColors} ${onHover} ${onDisabled} ${weight} ${className}`;

return (
<button
Expand Down
5 changes: 5 additions & 0 deletions src/consts/args.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum WARP_QUERY_PARAMS {
ORIGIN = 'origin',
DESTINATION = 'destination',
TOKEN = 'token',
}
10 changes: 10 additions & 0 deletions src/consts/warpRouteWhitelist.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { warpRouteConfigs } from '@hyperlane-xyz/registry';
import { assert, test } from 'vitest';
import { warpRouteWhitelist } from './warpRouteWhitelist';

test('warpRouteWhitelist', () => {
if (!warpRouteWhitelist) return;
for (const id of warpRouteWhitelist) {
assert(warpRouteConfigs[id], `No route with id ${id} found in registry.`);
}
});
5 changes: 2 additions & 3 deletions src/features/chains/ChainSelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useChainDisplayName } from './hooks';
type Props = {
name: string;
label: string;
onChange?: (id: ChainName) => void;
onChange?: (id: ChainName, fieldName: string) => void;
disabled?: boolean;
customListItemField: ChainSearchMenuProps['customListItemField'];
};
Expand All @@ -25,8 +25,7 @@ export function ChainSelectField({ name, label, onChange, disabled, customListIt
// Reset other fields on chain change
setFieldValue('recipient', '');
setFieldValue('amount', '');
setFieldValue('tokenIndex', undefined);
if (onChange) onChange(chainName);
if (onChange) onChange(chainName, name);
};

const [isModalOpen, setIsModalOpen] = useState(false);
Expand Down
10 changes: 10 additions & 0 deletions src/features/chains/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,13 @@ export function getNumRoutesWithSelectedChain(
data,
};
}

/**
* Return given chainName if it is valid, otherwise return undefined
*/
export function tryGetValidChainName(
chainName: string | null,
multiProvider: MultiProtocolProvider,
): string | undefined {
return chainName && multiProvider.tryGetChainName(chainName) ? chainName : undefined;
}
24 changes: 5 additions & 19 deletions src/features/tokens/TokenSelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ChevronIcon } from '@hyperlane-xyz/widgets';
import { useField, useFormikContext } from 'formik';
import { useEffect, useState } from 'react';
import { TokenIcon } from '../../components/icons/TokenIcon';

import { TransferFormValues } from '../transfer/types';
import { TokenListModal } from './TokenListModal';
import { getIndexForToken, getTokenByIndex, useWarpCore } from './hooks';
Expand All @@ -11,9 +12,10 @@ type Props = {
name: string;
disabled?: boolean;
setIsNft: (value: boolean) => void;
onChangeToken: (addressOrDenom: string) => void;
};

export function TokenSelectField({ name, disabled, setIsNft }: Props) {
export function TokenSelectField({ name, disabled, setIsNft, onChangeToken }: Props) {
const { values } = useFormikContext<TransferFormValues>();
const [field, , helpers] = useField<number | undefined>(name);
const [isModalOpen, setIsModalOpen] = useState(false);
Expand All @@ -24,29 +26,13 @@ export function TokenSelectField({ name, disabled, setIsNft }: Props) {
const { origin, destination } = values;
useEffect(() => {
const tokensWithRoute = warpCore.getTokensForRoute(origin, destination);
let newFieldValue: number | undefined;
let newIsAutomatic: boolean;
// No tokens available for this route
if (tokensWithRoute.length === 0) {
newFieldValue = undefined;
newIsAutomatic = true;
}
// Exactly one found
else if (tokensWithRoute.length === 1) {
newFieldValue = getIndexForToken(warpCore, tokensWithRoute[0]);
newIsAutomatic = true;
// Multiple possibilities
} else {
newFieldValue = undefined;
newIsAutomatic = false;
}
helpers.setValue(newFieldValue);
setIsAutomaticSelection(newIsAutomatic);
setIsAutomaticSelection(tokensWithRoute.length <= 1);
}, [warpCore, origin, destination, helpers]);

const onSelectToken = (newToken: IToken) => {
// Set the token address value in formik state
helpers.setValue(getIndexForToken(warpCore, newToken));
onChangeToken(newToken.addressOrDenom);
// Update nft state in parent
setIsNft(newToken.isNft());
};
Expand Down
22 changes: 21 additions & 1 deletion src/features/tokens/balances.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { IToken } from '@hyperlane-xyz/sdk';
import { IToken, MultiProtocolProvider, Token } from '@hyperlane-xyz/sdk';
import { isValidAddress } from '@hyperlane-xyz/utils';
import { useAccountAddressForChain } from '@hyperlane-xyz/widgets';
import { useQuery } from '@tanstack/react-query';
import { toast } from 'react-toastify';
import { useToastError } from '../../components/toast/useToastError';
import { logger } from '../../utils/logger';
import { useMultiProvider } from '../chains/hooks';
import { getChainDisplayName } from '../chains/utils';
import { TransferFormValues } from '../transfer/types';
import { useTokenByIndex } from './hooks';

Expand Down Expand Up @@ -41,3 +44,20 @@ export function useDestinationBalance({ destination, tokenIndex, recipient }: Tr
const connection = originToken?.getConnectionForChain(destination);
return useBalance(destination, connection?.token, recipient);
}

export async function getDestinationNativeBalance(
multiProvider: MultiProtocolProvider,
{ destination, recipient }: TransferFormValues,
) {
try {
const chainMetadata = multiProvider.getChainMetadata(destination);
const token = Token.FromChainMetadataNativeToken(chainMetadata);
const balance = await token.getBalance(multiProvider, recipient);
return balance.amount;
} catch (error) {
const msg = `Error checking recipient balance on ${getChainDisplayName(multiProvider, destination)}`;
logger.error(msg, error);
toast.error(msg);
return undefined;
}
}
44 changes: 44 additions & 0 deletions src/features/tokens/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,47 @@ export function tryFindToken(
return null;
}
}

function getTokenIndexFromChains(
warpCore: WarpCore,
addressOrDenom: string | null,
origin: string,
destination: string,
) {
// find routes
const tokensWithRoute = warpCore.getTokensForRoute(origin, destination);
// find provided token addressOrDenom
const queryToken = tokensWithRoute.find((token) => token.addressOrDenom === addressOrDenom);

// if found return index
if (queryToken) return getIndexForToken(warpCore, queryToken);
// if tokens route has only one route return that index
else if (tokensWithRoute.length === 1) return getIndexForToken(warpCore, tokensWithRoute[0]);
// if 0 or more than 1 then return undefined
return undefined;
}

export function getInitialTokenIndex(
warpCore: WarpCore,
addressOrDenom: string | null,
originQuery?: string,
destinationQuery?: string,
): number | undefined {
const firstToken = warpCore.tokens[0];
const connectedToken = firstToken.connections?.[0];

// origin query and destination query is defined
if (originQuery && destinationQuery)
return getTokenIndexFromChains(warpCore, addressOrDenom, originQuery, destinationQuery);

// if none of those are defined, use default values and pass token query
if (connectedToken)
return getTokenIndexFromChains(
warpCore,
addressOrDenom,
firstToken.chainName,
connectedToken.token.chainName,
);

return undefined;
}
44 changes: 44 additions & 0 deletions src/features/transfer/RecipientConfirmationModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Modal } from '@hyperlane-xyz/widgets';
import { useFormikContext } from 'formik';
import { SolidButton } from '../../components/buttons/SolidButton';
import { TransferFormValues } from './types';

export function RecipientConfirmationModal({
isOpen,
close,
onConfirm,
}: {
isOpen: boolean;
close: () => void;
onConfirm: () => void;
}) {
const { values } = useFormikContext<TransferFormValues>();
return (
<Modal
isOpen={isOpen}
close={close}
title="Confirm Recipient Address"
panelClassname="flex flex-col items-center p-4 gap-5"
>
<p className="text-center text-sm">
The recipient address has no funds on the destination chain. Is this address correct?
</p>
<p className="rounded-lg bg-primary-500/5 p-2 text-center text-sm">{values.recipient}</p>
<div className="flex items-center justify-center gap-12">
<SolidButton onClick={close} color="gray" className="min-w-24 px-4 py-1">
Cancel
</SolidButton>
<SolidButton
onClick={() => {
close();
onConfirm();
}}
color="primary"
className="min-w-24 px-4 py-1"
>
Continue
</SolidButton>
</div>
</Modal>
);
}
Loading

0 comments on commit 7663654

Please sign in to comment.