Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into trump
Browse files Browse the repository at this point in the history
  • Loading branch information
github-actions[bot] committed Jan 27, 2025
2 parents 7cfc050 + b3a3aed commit 7a0252c
Show file tree
Hide file tree
Showing 9 changed files with 1,015 additions and 17 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
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
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.`);
}
});
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/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>
);
}
41 changes: 33 additions & 8 deletions src/features/transfer/TransferTokenForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getAccountAddressAndPubKey,
useAccountAddressForChain,
useAccounts,
useModal,
} from '@hyperlane-xyz/widgets';
import BigNumber from 'bignumber.js';
import { Form, Formik, useFormikContext } from 'formik';
Expand All @@ -30,8 +31,13 @@ import { useStore } from '../store';
import { SelectOrInputTokenIds } from '../tokens/SelectOrInputTokenIds';
import { TokenSelectField } from '../tokens/TokenSelectField';
import { useIsApproveRequired } from '../tokens/approval';
import { useDestinationBalance, useOriginBalance } from '../tokens/balances';
import {
getDestinationNativeBalance,
useDestinationBalance,
useOriginBalance,
} from '../tokens/balances';
import { getIndexForToken, getTokenByIndex, useWarpCore } from '../tokens/hooks';
import { RecipientConfirmationModal } from './RecipientConfirmationModal';
import { useFetchMaxAmount } from './maxAmount';
import { TransferFormValues } from './types';
import { useRecipientBalanceWatcher } from './useBalanceWatcher';
Expand All @@ -49,12 +55,26 @@ export function TransferTokenForm() {
const [isReview, setIsReview] = useState(false);
// Flag for check current type of token
const [isNft, setIsNft] = useState(false);
// Modal for confirming address
const {
open: openConfirmationModal,
close: closeConfirmationModal,
isOpen: isConfirmationModalOpen,
} = useModal();

const validate = (values: TransferFormValues) => validateForm(warpCore, values, accounts);

const onSubmitForm = (values: TransferFormValues) => {
logger.debug('Reviewing transfer form values for:', values.origin, values.destination);
setIsReview(true);
const onSubmitForm = async (values: TransferFormValues) => {
logger.debug('Checking destination native balance for:', values.destination, values.recipient);
const balance = await getDestinationNativeBalance(multiProvider, values);
if (isNullish(balance)) return;
else if (balance > 0n) {
logger.debug('Reviewing transfer form values for:', values.origin, values.destination);
setIsReview(true);
} else {
logger.debug('Recipient has no balance on destination. Confirming address.');
openConfirmationModal();
}
};

return (
Expand All @@ -80,6 +100,11 @@ export function TransferTokenForm() {
isValidating={isValidating}
setIsReview={setIsReview}
/>
<RecipientConfirmationModal
isOpen={isConfirmationModalOpen}
close={closeConfirmationModal}
onConfirm={() => setIsReview(true)}
/>
</Form>
)}
</Formik>
Expand Down Expand Up @@ -276,7 +301,7 @@ function ButtonSection({
type="button"
color="primary"
onClick={() => setIsReview(false)}
classes="px-6 py-1.5"
className="px-6 py-1.5"
icon={<ChevronIcon direction="w" width={10} height={6} color={Color.white} />}
>
<span>Edit</span>
Expand All @@ -285,7 +310,7 @@ function ButtonSection({
type="button"
color="accent"
onClick={triggerTransactionsHandler}
classes="flex-1 px-3 py-1.5"
className="flex-1 px-3 py-1.5"
>
{`Send to ${chainDisplayName}`}
</SolidButton>
Expand Down Expand Up @@ -315,7 +340,7 @@ function MaxButton({ balance, disabled }: { balance?: TokenAmount; disabled?: bo
onClick={onClick}
color="accent"
disabled={disabled}
classes="text-xs absolute right-1 top-2.5 bottom-1 px-2 opacity-90 all:rounded"
className="absolute bottom-1 right-1 top-2.5 px-2 text-xs opacity-90 all:rounded"
>
{isLoading ? (
<div className="flex items-center">
Expand Down Expand Up @@ -345,7 +370,7 @@ function SelfButton({ disabled }: { disabled?: boolean }) {
onClick={onClick}
color="accent"
disabled={disabled}
classes="text-xs absolute right-1 top-2.5 bottom-1 px-2 opacity-90 all:rounded"
className="absolute bottom-1 right-1 top-2.5 px-2 text-xs opacity-90 all:rounded"
>
Self
</SolidButton>
Expand Down
7 changes: 7 additions & 0 deletions vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';

export default defineConfig({
plugins: [tsconfigPaths()],
test: {},
});
Loading

0 comments on commit 7a0252c

Please sign in to comment.