Merge pull request #5 from hyperlane-xyz/form-improvements

Form improvements and code cleanup
pull/6/head
J M Rossy 2 years ago committed by GitHub
commit d47ef943b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .eslintignore
  2. 22
      README.md
  3. 1
      next.config.js
  4. BIN
      public/background-texture2.png
  5. BIN
      public/background-texture3.png
  6. 28
      src/components/animation/Fade.tsx
  7. 7
      src/components/buttons/BackButton.tsx
  8. 41
      src/components/buttons/BorderedButton.tsx
  9. 31
      src/components/buttons/TextButton.tsx
  10. 8
      src/components/layout/HrDivider.tsx
  11. 25
      src/features/tokens/useTokenBalance.tsx
  12. 233
      src/features/transfer/TransferTokenForm.tsx
  13. 3
      src/images/icons/arrow-left-circle.svg
  14. 3
      src/images/icons/arrow-right-short.svg
  15. 3
      src/images/icons/arrow-right.svg
  16. 3
      src/images/icons/asterisk.svg
  17. 3
      src/images/icons/book.svg
  18. 4
      src/images/icons/briefcase.svg
  19. 4
      src/images/icons/bug.svg
  20. 5
      src/images/icons/clipboard-plus.svg
  21. 3
      src/images/icons/funnel.svg
  22. 1
      src/images/icons/house.svg
  23. 1
      src/images/icons/hub.svg
  24. 3
      src/images/icons/key.svg
  25. 1
      src/images/icons/lock.svg
  26. 3
      src/images/icons/paper-airplane.svg
  27. 1
      src/images/icons/search-off.svg
  28. 1
      src/images/icons/search.svg
  29. 3
      src/images/icons/shield-check.svg
  30. 1
      src/images/icons/shrug.svg
  31. 3
      src/images/icons/sliders.svg
  32. 1
      src/images/logos/discord-alt.svg
  33. 1
      src/images/logos/hyperlane-chevron-single-blue.svg
  34. 1
      src/images/logos/hyperlane-chevron.svg
  35. 6
      src/pages/index.tsx
  36. 37
      src/types.ts
  37. 30
      src/utils/objects.ts
  38. 28
      src/utils/queryParams.ts
  39. 1
      src/utils/string.ts
  40. 59
      src/utils/time.ts
  41. 11
      tailwind.config.js

@ -2,5 +2,6 @@ node_modules
dist
build
coverage
postcss.config.js
next.config.js
tailwind.config.js

@ -8,7 +8,7 @@ An interchain web app for bridging tokens with Hyperlane.
# Install dependencies
yarn
# Build source and generate types
# Build Next project
yarn build
```
@ -22,11 +22,25 @@ yarn dev
## Test
```sh
# Run all unit tests
yarn test
# Lint check code
yarn lint
# Check code types
yarn typecheck
```
## Format
```sh
# Format code using Prettier
yarn prettier
```
## Clean / Reset
```sh
# Delete build artifacts to start fresh
yarn clean
```
## Learn more

@ -21,6 +21,7 @@ const securityHeaders = [
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
// Note, causes a problem for firefox: https://github.com/MetaMask/metamask-extension/issues/3133
{
key: 'Content-Security-Policy',
value: `default-src 'self'; script-src 'self'${

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

@ -1,28 +0,0 @@
import { PropsWithChildren, useEffect, useState } from 'react';
export function Fade(props: PropsWithChildren<{ show: boolean }>) {
const { show, children } = props;
const [render, setRender] = useState(show);
useEffect(() => {
if (show) setRender(true);
}, [show]);
const onAnimationEnd = () => {
if (!show) setRender(false);
};
return render ? (
<div
style={{
animationName: show ? 'fadeIn' : 'fadeOut',
animationDuration: '1s',
//https://github.com/radix-ui/primitives/issues/1074#issuecomment-1089555751
animationFillMode: 'forwards',
}}
onAnimationEnd={onAnimationEnd}
>
{children}
</div>
) : null;
}

@ -1,7 +0,0 @@
import LeftArrow from '../../images/icons/arrow-left-circle.svg';
import { IconButton, IconButtonProps } from './IconButton';
export function BackButton(props: IconButtonProps) {
return <IconButton imgSrc={LeftArrow} title="Go back" {...props} />;
}

@ -1,41 +0,0 @@
import { PropsWithChildren, ReactElement } from 'react';
interface ButtonProps {
type?: 'submit' | 'reset' | 'button';
onClick?: () => void;
classes?: string;
bold?: boolean;
disabled?: boolean;
icon?: ReactElement;
title?: string;
}
export function BorderedButton(props: PropsWithChildren<ButtonProps>) {
const { type, onClick, classes, bold, icon, disabled, title } = props;
const base = 'border border-black rounded transition-all';
const onHover = 'hover:border-gray-500 hover:text-gray-500';
const onDisabled = 'disabled:border-gray-300 disabled:text-gray-300';
const onActive = 'active:border-gray-400 active:text-gray-400';
const weight = bold ? 'font-semibold' : '';
const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${weight} ${classes}`;
return (
<button
onClick={onClick}
type={type ?? 'button'}
disabled={disabled ?? false}
title={title}
className={allClasses}
>
{icon ? (
<div className="flex items-center">
{props.icon}
{props.children}
</div>
) : (
<>{props.children}</>
)}
</button>
);
}

@ -1,31 +0,0 @@
import { PropsWithChildren } from 'react';
export interface TextButtonProps {
classes?: string;
onClick?: () => void;
disabled?: boolean;
type?: 'button' | 'submit';
passThruProps?: any;
}
export function TextButton(props: PropsWithChildren<TextButtonProps>) {
const { classes, onClick, disabled, type, children, passThruProps } = props;
const base = 'flex place-content-center transition-all';
const onHover = 'hover:opacity-70';
const onDisabled = 'disabled:opacity-50';
const onActive = 'active:opacity-60';
const allClasses = `${base} ${onHover} ${onDisabled} ${onActive} ${classes}`;
return (
<button
onClick={onClick}
type={type || 'button'}
disabled={disabled ?? false}
className={allClasses}
{...passThruProps}
>
{children}
</button>
);
}

@ -1,8 +0,0 @@
interface Props {
classes?: string;
}
export function HrDivider(props: Props) {
const { classes } = props;
return <hr className={`w-full h-px border-none bg-gray-300 ${classes}`} />;
}

@ -1,10 +1,19 @@
import { useQuery } from '@tanstack/react-query';
import { QueryClient, useQuery } from '@tanstack/react-query';
import { useAccount } from 'wagmi';
import { logger } from '../../utils/logger';
import { getErc20Contract } from '../contracts/erc20';
import { getProvider } from '../providers';
export function getTokenBalanceKey(
chainId: number,
tokenAddress: Address,
isConnected: boolean,
accountAddress?: Address,
) {
return ['tokenBalance', chainId, tokenAddress, accountAddress, isConnected];
}
export function useTokenBalance(chainId: number, tokenAddress: Address) {
const { address: accountAddress, isConnected } = useAccount();
@ -13,7 +22,7 @@ export function useTokenBalance(chainId: number, tokenAddress: Address) {
isError: hasError,
data: balance,
} = useQuery(
['tokenBalance', chainId, tokenAddress, accountAddress, isConnected],
getTokenBalanceKey(chainId, tokenAddress, isConnected, accountAddress),
() => {
if (!chainId || !tokenAddress || !accountAddress || !isConnected) return null;
return fetchTokenBalance(chainId, tokenAddress, accountAddress);
@ -24,6 +33,18 @@ export function useTokenBalance(chainId: number, tokenAddress: Address) {
return { isFetching, hasError, balance };
}
export function getCachedTokenBalance(
queryClient: QueryClient,
chainId: number,
tokenAddress: Address,
isConnected: boolean,
accountAddress?: Address,
) {
return queryClient.getQueryData(
getTokenBalanceKey(chainId, tokenAddress, isConnected, accountAddress),
) as string | undefined;
}
async function fetchTokenBalance(chainId: number, tokenAddress: Address, accountAddress: Address) {
logger.debug(
`Fetching balance for account ${accountAddress} token ${tokenAddress} on chain ${chainId}`,

@ -1,5 +1,7 @@
import { useQueryClient } from '@tanstack/react-query';
import { Form, Formik, useFormikContext } from 'formik';
import { useState } from 'react';
import { useAccount } from 'wagmi';
import { chainIdToMetadata, chainMetadata } from '@hyperlane-xyz/sdk';
@ -10,6 +12,7 @@ import { ChevronIcon } from '../../components/icons/Chevron';
import { HyperlaneChevron, HyperlaneWideChevron } from '../../components/icons/HyperlaneChevron';
import { TextField } from '../../components/input/TextField';
import { Card } from '../../components/layout/Card';
import { config } from '../../consts/config';
import GearIcon from '../../images/icons/gear.svg';
import SwapIcon from '../../images/icons/swap.svg';
import { Color } from '../../styles/Color';
@ -19,7 +22,7 @@ import { getChainDisplayName, getChainEnvironment } from '../../utils/chains';
import { logger } from '../../utils/logger';
import { ChainSelectField } from '../chains/ChainSelectField';
import { TokenSelectField } from '../tokens/TokenSelectField';
import { useTokenBalance } from '../tokens/useTokenBalance';
import { getCachedTokenBalance, useTokenBalance } from '../tokens/useTokenBalance';
import { TransferTransactionsModal } from './TransferTransactionsModal';
import { TransferFormValues } from './types';
@ -47,6 +50,8 @@ export function TransferTokenForm() {
setIsReview(false);
};
const queryClient = useQueryClient();
const { address: accountAddress, isConnected } = useAccount();
const validateForm = ({
sourceChainId,
destinationChainId,
@ -64,11 +69,6 @@ export function TransferTokenForm() {
if (getChainEnvironment(sourceChainId) !== getChainEnvironment(destinationChainId)) {
return { destinationChainId: 'Invalid chain combination' };
}
// TODO check balance and check non-zero
const parsedAmount = tryParseAmount(amount);
if (!parsedAmount || parsedAmount.lte(0)) {
return { amount: 'Invalid amount' };
}
if (!isValidAddress(recipientAddress)) {
return { recipientAddress: 'Invalid recipient' };
}
@ -78,6 +78,20 @@ export function TransferTokenForm() {
if (!isValidAddress(hypCollateralAddress)) {
return { tokenAddress: 'Invalid collateral token' };
}
const parsedAmount = tryParseAmount(amount);
if (!parsedAmount || parsedAmount.lte(0)) {
return { amount: 'Invalid amount' };
}
const cachedBalance = getCachedTokenBalance(
queryClient,
sourceChainId,
tokenAddress,
isConnected,
accountAddress,
);
if (cachedBalance && parsedAmount.gt(cachedBalance) && !config.debug) {
return { amount: 'Insufficient balance' };
}
return {};
};
@ -110,59 +124,56 @@ export function TransferTokenForm() {
validateOnBlur={false}
>
{({ values }) => (
<>
<Form className="flex flex-col items-stretch w-full mt-2">
<div className="flex items-center justify-center space-x-10">
<ChainSelectField name="sourceChainId" label="From" disabled={isReview} />
<div className="flex flex-col items-center">
<div className="flex mb-6 space-x-1.5">
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
</div>
<SwapChainsButton disabled={isReview} />
<Form className="flex flex-col items-stretch w-full mt-2">
<div className="flex items-center justify-center space-x-10">
<ChainSelectField name="sourceChainId" label="From" disabled={isReview} />
<div className="flex flex-col items-center">
<div className="flex mb-6 space-x-1.5">
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
<HyperlaneChevron
width="17"
height="100%"
direction="e"
color={Color.lightGray}
/>
</div>
<ChainSelectField name="destinationChainId" label="To" disabled={isReview} />
<SwapChainsButton disabled={isReview} />
</div>
<div className="mt-3 flex justify-between space-x-4">
<div className="flex-1">
<label
htmlFor="tokenAddress"
className="block uppercase text-sm text-gray-500 pl-0.5"
>
ERC-20 Token
<ChainSelectField name="destinationChainId" label="To" disabled={isReview} />
</div>
<div className="mt-3 flex justify-between space-x-4">
<div className="flex-1">
<label
htmlFor="tokenAddress"
className="block uppercase text-sm text-gray-500 pl-0.5"
>
ERC-20 Token
</label>
<TokenSelectField
name="tokenAddress"
chainFieldName="sourceChainId"
disabled={isReview}
/>
</div>
<div className="flex-1">
<div className="flex justify-between pr-1">
<label htmlFor="amount" className="block uppercase text-sm text-gray-500 pl-0.5">
Amount
</label>
<TokenSelectField
name="tokenAddress"
chainFieldName="sourceChainId"
disabled={isReview}
/>
<TokenBalance />
</div>
<div className="flex-1">
<div className="flex justify-between pr-1">
<label
htmlFor="amount"
className="block uppercase text-sm text-gray-500 pl-0.5"
>
Amount
</label>
<TokenBalance disabled={isReview} />
</div>
<div className="relative w-full">
<TextField
name="amount"
placeholder="0.00"
@ -171,51 +182,55 @@ export function TransferTokenForm() {
step="any"
disabled={isReview}
/>
<MaxButton disabled={isReview} />
</div>
</div>
<div className="mt-4">
<label
htmlFor="recipientAddress"
className="block uppercase text-sm text-gray-500 pl-0.5"
>
Recipient Address
</label>
</div>
<div className="mt-4">
<label
htmlFor="recipientAddress"
className="block uppercase text-sm text-gray-500 pl-0.5"
>
Recipient Address
</label>
<div className="relative w-full">
<TextField
name="recipientAddress"
placeholder="0x123456..."
classes="w-full"
disabled={isReview}
/>
<SelfButton disabled={isReview} />
</div>
<ReviewDetails visible={isReview} />
{!isReview ? (
<ConnectAwareSubmitButton text="Continue" classes="mt-4 px-3 py-1.5" />
) : (
<div className="mt-4 flex items-center justify-between space-x-4">
<SolidButton
type="button"
color="gray"
onClick={onClickEdit}
classes="px-6 py-1.5"
icon={<ChevronIcon direction="w" width={13} color={Color.primaryBlue} />}
>
<span>Edit</span>
</SolidButton>
<SolidButton
type="button"
color="blue"
onClick={() => triggerTransactions(values)}
classes="flex-1 px-3 py-1.5"
>
{`Send to ${getChainDisplayName(values.destinationChainId)}`}
</SolidButton>
</div>
)}
</Form>
<TransferTransactionsModal isOpen={showTxModal} close={hideModal} />
</>
</div>
<ReviewDetails visible={isReview} />
{!isReview ? (
<ConnectAwareSubmitButton text="Continue" classes="mt-4 px-3 py-1.5" />
) : (
<div className="mt-4 flex items-center justify-between space-x-4">
<SolidButton
type="button"
color="gray"
onClick={onClickEdit}
classes="px-6 py-1.5"
icon={<ChevronIcon direction="w" width={13} color={Color.primaryBlue} />}
>
<span>Edit</span>
</SolidButton>
<SolidButton
type="button"
color="blue"
onClick={() => triggerTransactions(values)}
classes="flex-1 px-3 py-1.5"
>
{`Send to ${getChainDisplayName(values.destinationChainId)}`}
</SolidButton>
</div>
)}
</Form>
)}
</Formik>
<TransferTransactionsModal isOpen={showTxModal} close={hideModal} />
</Card>
);
}
@ -243,7 +258,14 @@ function SwapChainsButton({ disabled }: { disabled?: boolean }) {
);
}
function TokenBalance({ disabled }: { disabled?: boolean }) {
function TokenBalance() {
const { values } = useFormikContext<TransferFormValues>();
const { balance } = useTokenBalance(values.sourceChainId, values.tokenAddress);
const rounded = fromWeiRounded(balance);
return <div className="text-xs text-gray-500">{`Balance: ${rounded}`}</div>;
}
function MaxButton({ disabled }: { disabled?: boolean }) {
const { values, setFieldValue } = useFormikContext<TransferFormValues>();
const { balance } = useTokenBalance(values.sourceChainId, values.tokenAddress);
const rounded = fromWeiRounded(balance);
@ -251,13 +273,34 @@ function TokenBalance({ disabled }: { disabled?: boolean }) {
if (balance && !disabled) setFieldValue('amount', rounded);
};
return (
<button
<SolidButton
type="button"
onClick={onClick}
className={`text-xs text-gray-500 ${
!balance && 'opacity-0 cursor-default'
} transition-all duration-300`}
>{`Balance: ${rounded}`}</button>
color="gray"
disabled={disabled}
classes="text-xs rounded-sm absolute right-0.5 top-2 bottom-0.5 px-2"
>
MAX
</SolidButton>
);
}
function SelfButton({ disabled }: { disabled?: boolean }) {
const { address } = useAccount();
const { setFieldValue } = useFormikContext<TransferFormValues>();
const onClick = () => {
if (address && !disabled) setFieldValue('recipientAddress', address);
};
return (
<SolidButton
type="button"
onClick={onClick}
color="gray"
disabled={disabled}
classes="text-xs rounded-sm absolute right-0.5 top-2 bottom-0.5 px-1.5"
>
SELF
</SolidButton>
);
}

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#2E3338" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8zm15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-4.5-.5a.5.5 0 0 1 0 1H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 333 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 293 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-arrow-right" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 289 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-asterisk" viewBox="0 0 16 16">
<path d="M8 0a1 1 0 0 1 1 1v5.268l4.562-2.634a1 1 0 1 1 1 1.732L10 8l4.562 2.634a1 1 0 1 1-1 1.732L9 9.732V15a1 1 0 1 1-2 0V9.732l-4.562 2.634a1 1 0 1 1-1-1.732L6 8 1.438 5.366a1 1 0 0 1 1-1.732L7 6.268V1a1 1 0 0 1 1-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 358 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book-half" viewBox="0 0 16 16">
<path d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
</svg>

Before

Width:  |  Height:  |  Size: 649 B

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-briefcase-fill" viewBox="0 0 16 16">
<path d="M6.5 1A1.5 1.5 0 0 0 5 2.5V3H1.5A1.5 1.5 0 0 0 0 4.5v1.384l7.614 2.03a1.5 1.5 0 0 0 .772 0L16 5.884V4.5A1.5 1.5 0 0 0 14.5 3H11v-.5A1.5 1.5 0 0 0 9.5 1h-3zm0 1h3a.5.5 0 0 1 .5.5V3H6v-.5a.5.5 0 0 1 .5-.5z"/>
<path d="M0 12.5A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5V6.85L8.129 8.947a.5.5 0 0 1-.258 0L0 6.85v5.65z"/>
</svg>

Before

Width:  |  Height:  |  Size: 473 B

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M4.978.855a.5.5 0 1 0-.956.29l.41 1.352A4.985 4.985 0 0 0 3 6h10a4.985 4.985 0 0 0-1.432-3.503l.41-1.352a.5.5 0 1 0-.956-.29l-.291.956A4.978 4.978 0 0 0 8 1a4.979 4.979 0 0 0-2.731.811l-.29-.956z"/>
<path d="M13 6v1H8.5v8.975A5 5 0 0 0 13 11h.5a.5.5 0 0 1 .5.5v.5a.5.5 0 1 0 1 0v-.5a1.5 1.5 0 0 0-1.5-1.5H13V9h1.5a.5.5 0 0 0 0-1H13V7h.5A1.5 1.5 0 0 0 15 5.5V5a.5.5 0 0 0-1 0v.5a.5.5 0 0 1-.5.5H13zm-5.5 9.975V7H3V6h-.5a.5.5 0 0 1-.5-.5V5a.5.5 0 0 0-1 0v.5A1.5 1.5 0 0 0 2.5 7H3v1H1.5a.5.5 0 0 0 0 1H3v1h-.5A1.5 1.5 0 0 0 1 11.5v.5a.5.5 0 1 0 1 0v-.5a.5.5 0 0 1 .5-.5H3a5 5 0 0 0 4.5 4.975z"/>
</svg>

Before

Width:  |  Height:  |  Size: 696 B

@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 7a.5.5 0 0 1 .5.5V9H10a.5.5 0 0 1 0 1H8.5v1.5a.5.5 0 0 1-1 0V10H6a.5.5 0 0 1 0-1h1.5V7.5A.5.5 0 0 1 8 7z"/>
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 614 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel-fill" viewBox="0 0 16 16">
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 313 B

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-house-door-fill" viewBox="0 0 16 16"><path d="M6.5 14.5V11c0-.2.3-.5.5-.5h2c.3 0 .5.3.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.1-.4L13 5.8V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.3L8.4 1a.5.5 0 0 0-.8 0l-6 6a.5.5 0 0 0-.1.4v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5z"/></svg>

Before

Width:  |  Height:  |  Size: 362 B

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#010101" d="M11.7 46q-2.4 0-4-1.7-1.7-1.6-1.7-4t1.7-4 4-1.7l1.2.1 1.1.4 4.2-5.3q-.9-1.1-1.4-2.6t-.3-3.1l-6-2q-.8 1.2-2 1.9t-2.8.7q-2.4 0-4-1.7Q0 21.4 0 19t1.7-4 4-1.7q2.4 0 4 1.6t1.7 4.1v.2l6.1 2.1q.9-1.6 2.2-2.5 1.3-.8 2.8-1.1v-6.5q-2-.6-3-2.1-1.2-1.6-1.2-3.4 0-2.4 1.7-4T24 0q2.4 0 4 1.6t1.7 4.1q0 1.8-1.1 3.4t-3.1 2.1v6.5q1.6.3 2.9 1.1t2.2 2.5l6-2.1V19q0-2.4 1.7-4t4-1.7q2.4 0 4 1.6T48 19q0 2.4-1.6 4-1.7 1.7-4.1 1.7-1.6 0-2.9-.7t-1.9-2l-6 2.1q.2 1.6-.3 3t-1.4 2.7l4.2 5.3 1-.4 1.3-.1q2.4 0 4 1.6t1.7 4.1q0 2.4-1.6 4-1.7 1.7-4.1 1.7-2.4 0-4-1.7-1.7-1.6-1.7-4 0-1 .3-1.8t.8-1.6l-4.3-5.2q-1.6.8-3.4.8-1.9 0-3.5-.8L16.4 37q.5.8.7 1.5.3.8.3 1.8 0 2.4-1.6 4Q14 46 11.6 46Zm-6-24.3q1.1 0 2-.8.7-.8.7-1.9t-.8-2q-.8-.7-1.9-.7t-2 .8Q3 17.9 3 19t.8 2q.8.7 1.9.7Zm6 21.3q1.1 0 2-.8.7-.8.7-1.9t-.8-2q-.8-.7-1.9-.7t-2 .8q-.7.8-.7 1.9t.8 2q.8.7 1.9.7ZM24 8.4q1.1 0 2-.8.7-.8.7-1.9t-.8-2Q25.1 3 24 3t-2 .8q-.7.8-.7 1.9t.8 2q.8.7 1.9.7Zm0 21.1q1.9 0 3.2-1.3 1.3-1.3 1.3-3.2 0-1.9-1.3-3.2-1.3-1.3-3.2-1.3-1.9 0-3.2 1.3-1.3 1.3-1.3 3.2 0 1.9 1.3 3.2 1.3 1.3 3.3 1.3ZM36.4 43q1.1 0 2-.8.7-.8.7-1.9t-.8-2q-.8-.7-1.9-.7t-2 .8q-.7.8-.7 1.9t.8 2q.8.7 1.9.7Zm6-21.3q1.1 0 2-.8.7-.8.7-1.9t-.8-2q-.8-.7-1.9-.7t-2 .8q-.7.8-.7 1.9t.8 2q.8.7 1.9.7ZM24 5.7ZM5.7 19ZM24 25Zm18.3-6ZM11.7 40.3Zm24.6 0Z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-key-fill" viewBox="0 0 16 16">
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2zM2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 285 B

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 18"><path d="M7.14 1.13c.76 0 1.49.23 2.02.65.54.43.84 1 .84 1.6v4.5H4.29v-4.5c0-.6.3-1.17.83-1.6a3.29 3.29 0 0 1 2.02-.66Zm4.29 6.75v-4.5c0-.9-.45-1.76-1.26-2.4C9.37.37 8.28 0 7.14 0 6.01 0 4.92.36 4.11.99c-.8.63-1.25 1.49-1.25 2.38v4.5c-.76 0-1.49.24-2.02.66-.54.43-.84 1-.84 1.6v5.62c0 .6.3 1.17.84 1.6.53.41 1.26.65 2.02.65h8.57c.76 0 1.48-.24 2.02-.66.53-.42.84-1 .84-1.59v-5.63c0-.6-.3-1.16-.84-1.59a3.29 3.29 0 0 0-2.02-.65Z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 520 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083l6-15Zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471-.47 1.178Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 373 B

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M13.5 44q-3.9 0-6.7-2.8Q4 38.4 4 34.5q0-3.9 2.8-6.7Q9.6 25 13.5 25q3.9 0 6.7 2.8 2.8 2.8 2.8 6.7 0 3.9-2.8 6.7-2.8 2.8-6.7 2.8Zm28.3-2L28.65 28.85q-.7.55-1.575 1.075-.875.525-1.675.875-.25-.7-.55-1.425-.3-.725-.7-1.325 2.7-1.05 4.575-3.475T30.6 18.8q0-4.05-2.85-6.925T20.85 9q-4.1 0-6.975 2.875T11 18.8q0 .85.175 1.775.175.925.425 1.575-.65.1-1.45.325-.8.225-1.45.525-.35-.9-.525-2Q8 19.9 8 18.8q0-5.35 3.75-9.075Q15.5 6 20.85 6q5.3 0 9.025 3.75Q33.6 13.5 33.6 18.8q0 2.15-.75 4.25T30.8 26.7L44 39.8Zm-31.75-2.8 3.45-3.45 3.4 3.4L18.05 38l-3.45-3.45L18.15 31 17 29.85l-3.5 3.5-3.5-3.5L8.85 31l3.5 3.5-3.5 3.5Z"/></svg>

Before

Width:  |  Height:  |  Size: 690 B

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="6 5.949999809265137 36 36"><path d="M39.8 41.95 26.65 28.8q-1.5 1.3-3.5 2.025-2 .725-4.25.725-5.4 0-9.15-3.75T6 18.75q0-5.3 3.75-9.05 3.75-3.75 9.1-3.75 5.3 0 9.025 3.75 3.725 3.75 3.725 9.05 0 2.15-.7 4.15-.7 2-2.1 3.75L42 39.75Zm-20.95-13.4q4.05 0 6.9-2.875Q28.6 22.8 28.6 18.75t-2.85-6.925Q22.9 8.95 18.85 8.95q-4.1 0-6.975 2.875T9 18.75q0 4.05 2.875 6.925t6.975 2.875Z"></path></svg>

Before

Width:  |  Height:  |  Size: 459 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 0c-.69 0-1.843.265-2.928.56-1.11.3-2.229.655-2.887.87a1.54 1.54 0 0 0-1.044 1.262c-.596 4.477.787 7.795 2.465 9.99a11.777 11.777 0 0 0 2.517 2.453c.386.273.744.482 1.048.625.28.132.581.24.829.24s.548-.108.829-.24a7.159 7.159 0 0 0 1.048-.625 11.775 11.775 0 0 0 2.517-2.453c1.678-2.195 3.061-5.513 2.465-9.99a1.541 1.541 0 0 0-1.044-1.263 62.467 62.467 0 0 0-2.887-.87C9.843.266 8.69 0 8 0zm2.146 5.146a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 7.793l2.646-2.647z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 638 B

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 77 20"><path d="M.5 3.5V2.2h5.8v1.3H.5Zm12.8 12.8h-2.2l-5.7-15h2.2l5.7 15Zm-.4 2.2v-1h10v1h-10Zm13.8 1.3a31.9 31.9 0 0 1-2-4.1 16.6 16.6 0 0 1-.9-5.1c0-1.6.3-3.2.8-4.8.5-1.5 1.2-3 2.1-4.4h1.9a18 18 0 0 0-1.9 14c.4 1.5 1 3 1.9 4.4h-1.9ZM37.5 1l-1.5.5c.5 1 1.6 4.1 2 5.3l1.5-.6C39.2 5.1 38 2 37.5 1Zm9 1.3-2-.6c-.3 3-1.6 6.2-3.1 8.3-2 2.5-5 4.4-7.9 5.3l1.4 1.4A17.5 17.5 0 0 0 46 3.4l.3-1.1Zm-14.6-.1-1.5.5a56 56 0 0 1 2.2 5.5l1.6-.5a89 89 0 0 0-2.3-5.5Zm18.2-.8a24.2 24.2 0 0 1 2 4.1 14.5 14.5 0 0 1 .9 5.1 17.9 17.9 0 0 1-2.9 9.2h-1.8a19.6 19.6 0 0 0 2.4-9.2 18.8 18.8 0 0 0-2.4-9.2H50ZM54 18.5v-1h10v1H54ZM69.3 1.4h2.2l-5.8 15h-2.2l5.8-15Zm1.3 2.1V2.2h5.7v1.3h-5.7Z" fill="#010101"/></svg>

Before

Width:  |  Height:  |  Size: 755 B

@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#2E3338" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M11.5 2a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM9.05 3a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0V3h9.05zM4.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM2.05 8a2.5 2.5 0 0 1 4.9 0H16v1H6.95a2.5 2.5 0 0 1-4.9 0H0V8h2.05zm9.45 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm-2.45 1a2.5 2.5 0 0 1 4.9 0H16v1h-2.05a2.5 2.5 0 0 1-4.9 0H0v-1h9.05z"/>
</svg>

Before

Width:  |  Height:  |  Size: 476 B

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 71 55"><g clip-path="url(#a)"><path d="M60.1 4.9A58.5 58.5 0 0 0 45.4.5l-1.8 3.7a54 54 0 0 0-16.2 0 37.4 37.4 0 0 0-2-3.8A58.4 58.4 0 0 0 10.7 5 60 60 0 0 0 .4 45.6a58.9 58.9 0 0 0 18 8.8 42 42 0 0 0 3.6-5.9l-.1-.3c-2-.7-3.8-1.6-5.6-2.6a.2.2 0 0 1 0-.4 30.3 30.3 0 0 0 1.3-.9 42 42 0 0 0 36 0l1 1c.2 0 .2.2 0 .3-1.7 1-3.6 1.9-5.5 2.6a47.2 47.2 0 0 0 3.8 6.3 58.7 58.7 0 0 0 17.8-9.1A59.5 59.5 0 0 0 60 4.9ZM23.7 37.3c-3.5 0-6.4-3.2-6.4-7.1 0-4 2.9-7.2 6.4-7.2 3.6 0 6.5 3.3 6.4 7.2 0 4-2.8 7.1-6.4 7.1Zm23.6 0c-3.5 0-6.4-3.2-6.4-7.1 0-4 2.9-7.2 6.4-7.2 3.6 0 6.5 3.3 6.4 7.2 0 4-2.8 7.1-6.4 7.1Z" fill="#010101"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h71v55H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 766 B

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="1.2370047569274902 1 139.0608673095703 322"><path d="M6.3 1h61.3a20 20 0 0 1 18.7 13L140 158.3a5 5 0 0 1 0 3.4l-.3.9-53.5 147.2A20 20 0 0 1 67.4 323H6.2a5 5 0 0 1-4.7-6.6l55.2-158.1L1.7 7.7A5 5 0 0 1 6.2 1Z" fill="#025AA1"></path></svg>

Before

Width:  |  Height:  |  Size: 297 B

@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 301 324"><path d="M166.2 1h61.2A20 20 0 0 1 246 14L300 158.3a5 5 0 0 1 0 3.4l-.3.9L246 309.8a20 20 0 0 1-18.8 13.2H166a5 5 0 0 1-4.7-6.6l55.2-158.1-55-150.6a5 5 0 0 1 4.7-6.7Z" fill="#010101"/><path d="M6.3 1h61.3a20 20 0 0 1 18.7 13L140 158.3a5 5 0 0 1 0 3.4l-.3.9-53.5 147.2A20 20 0 0 1 67.4 323H6.2a5 5 0 0 1-4.7-6.6l55.2-158.1L1.7 7.7A5 5 0 0 1 6.2 1Z" fill="#010101"/><path d="m216.5 158.3-55-150.6a5 5 0 0 1 4.7-6.7h61.2A20 20 0 0 1 246 14L300 158.3a5 5 0 0 1 0 3.4L246 309.8a20 20 0 0 1-18.8 13.2H166a5 5 0 0 1-4.7-6.6l26.4-75.8 28.8-82.3Zm0 0-55 158a5 5 0 0 0 4.7 6.7h61m-10.7-164.7-55-150.5a5 5 0 0 1 4.7-6.8h61.2m-10.9 157.3-55.2 158a5 5 0 0 0 4.7 6.7h61.3m-10.8-164.7-55-150.6a5 5 0 0 1 4.7-6.7h61.2m0 0A20 20 0 0 1 246 14L300 158.3m0 0a5 5 0 0 1 0 3.4m0-3.4v2.5a5 5 0 0 1-.3 1.8m.3-.9L246 309.8m53.8-148-.3.8M246 309.8a20 20 0 0 1-18.8 13.2m18.8-13.2 53.5-147.2m-243-4.3-55.1 158a5 5 0 0 0 4.7 6.7h61.2a20 20 0 0 0 18.8-13.2l53.5-147.2.4-.9a5 5 0 0 0 0-3.4L86.2 14A20 20 0 0 0 67.6 1H6.3a5 5 0 0 0-4.7 6.7l55 150.6Z" stroke="#010101"/></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

@ -10,7 +10,7 @@ import XCircle from '../images/icons/x-circle.svg';
const Home: NextPage = () => {
return (
<div className="space-y-4">
<div className="space-y-3">
<InfoCard />
<TransferTokenForm />
</div>
@ -25,8 +25,8 @@ function InfoCard() {
<h2 className="text-white text-lg">Bridge Tokens Permissionlessly with Hyperlane!</h2>
<div className="flex items-end justify-between">
<p className="text-white mt-1.5 text-sm max-w-[70%]">
Send tokens across chains in seconds or make tokens interchain-ready with just a few
clicks.
Send tokens across chains in seconds or make tokens interchain-ready with just a few lines
of code.
</p>
<a
href={links.docs}

@ -1,37 +0,0 @@
// Modeled after ethers.providers.TransactionReceipt
export interface PartialTransactionReceipt {
from: Address;
transactionHash: string;
blockNumber: number;
gasUsed: number;
timestamp: number;
}
// TODO consider reconciling with SDK's MessageStatus
export enum MessageStatus {
Pending = 'pending',
Delivered = 'delivered',
Failing = 'failing',
}
export interface MessageStub {
id: number;
status: MessageStatus;
sender: Address;
recipient: Address;
originDomainId: number;
destinationDomainId: number;
originChainId: number;
destinationChainId: number;
originTimestamp: number; // Note, equivalent to timestamp in originTransaction
destinationTimestamp?: number; // Note, equivalent to timestamp in destinationTransaction
}
export interface Message extends MessageStub {
body: string;
decodedBody?: string;
leafIndex: number;
hash: string; // message hash, not related to txs
originTransaction: PartialTransactionReceipt;
destinationTransaction?: PartialTransactionReceipt;
}

@ -1,30 +0,0 @@
export function invertKeysAndValues(data: any) {
return Object.fromEntries(Object.entries(data).map(([key, value]) => [value, key]));
}
// Get the subset of the object from key list
export function pick<K extends string | number, V = any>(obj: Record<K, V>, keys: K[]) {
const ret: Partial<Record<K, V>> = {};
for (const key of keys) {
ret[key] = obj[key];
}
return ret as Record<K, V>;
}
// Remove a particular key from an object if it exists
export function omit<K extends string | number, V = any>(obj: Record<K, V>, key: K) {
const ret: Partial<Record<K, V>> = {};
for (const k of Object.keys(obj)) {
if (k === key) continue;
ret[k] = obj[k];
}
return ret as Record<K, V>;
}
// Returns an object with the keys as values from an array and value set to true
export function arrayToObject(keys: Array<string | number>, val = true) {
return keys.reduce((result, k) => {
result[k] = val;
return result;
}, {});
}

@ -1,28 +0,0 @@
import type { ParsedUrlQuery } from 'querystring';
import { logger } from './logger';
// To make Next's awkward query param typing more convenient
export function getQueryParamString(query: ParsedUrlQuery, key: string, defaultVal = '') {
if (!query) return defaultVal;
const val = query[key];
if (val && typeof val === 'string') return val;
else return defaultVal;
}
// Circumventing Next's router.replace method here because
// it's async and causes race conditions btwn components.
// This will only modify the url but not trigger any routing
export function replacePathParam(key: string, val: string) {
try {
const url = new URL(window.location.href);
if (val) {
url.searchParams.set(key, val);
} else {
url.searchParams.delete(key);
}
window.history.replaceState('', '', url);
} catch (error) {
logger.error('Error replacing path param', error);
}
}

@ -4,7 +4,6 @@ export function toTitleCase(str: string) {
});
}
// TODO add unit tests
// Only allows letters and numbers
const alphanumericRgex = /[^a-zA-Z0-9]/gi;
export function sanitizeString(str: string) {

@ -1,59 +0,0 @@
// Inspired by https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
export function getHumanReadableTimeString(timestamp: number) {
const seconds = Math.floor((Date.now() - timestamp) / 1000);
if (seconds <= 1) {
return 'Just now';
}
if (seconds <= 60) {
return `${seconds} seconds ago`;
}
const minutes = Math.floor(seconds / 60);
if (minutes <= 1) {
return '1 minute ago';
}
if (minutes < 60) {
return `${minutes} minutes ago`;
}
const hours = Math.floor(minutes / 60);
if (hours <= 1) {
return '1 hour ago';
}
if (hours < 24) {
return `${hours} hours ago`;
}
const date = new Date(timestamp);
return date.toLocaleDateString();
}
export function getHumanReadableDuration(ms: number, minSec?: number) {
let seconds = Math.round(ms / 1000);
if (minSec) {
seconds = Math.max(seconds, minSec);
}
if (seconds <= 60) {
return `${seconds} sec`;
}
const minutes = Math.floor(seconds / 60);
if (minutes < 60) {
return `${minutes} min`;
}
const hours = Math.floor(minutes / 60);
return `${hours} hr`;
}
export function getDateTimeString(timestamp: number) {
const date = new Date(timestamp);
return `${date.toLocaleTimeString()} ${date.toLocaleDateString()}`;
}
// Adjusts a timestamp forward/backward based on
// the local time's for the timezone offset
export function adjustToUtcTime(timestamp: number) {
const offsetMs = new Date().getTimezoneOffset() * 60_000;
const adjusted = new Date(timestamp + offsetMs);
return adjusted.toISOString();
}

@ -31,17 +31,6 @@ module.exports = {
800: '#002440',
900: '#001220',
},
beige: {
100: '#F6F4F1',
200: '#F5F2EF',
300: '#F3F0ED',
400: '#F2EEEB',
500: '#F1EDE9',
600: '#D8D5D1',
700: '#C0BDBA',
800: '#A8A5A3',
900: '#908E8B',
},
red: {
100: '#EBBAB8',
200: '#DF8D8A',

Loading…
Cancel
Save