Skip to content

Commit

Permalink
Port over improvements for IGP quoting and white-labelling from injec…
Browse files Browse the repository at this point in the history
…tive branch
  • Loading branch information
jmrossy committed Jan 29, 2024
1 parent 22fb637 commit 5f53d38
Show file tree
Hide file tree
Showing 25 changed files with 192 additions and 116 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ name: ci
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [main, nautilus, nexus]
branches: [main, nautilus, nexus, injective]
pull_request:
branches: [main, nautilus, nexus]
branches: [main, nautilus, nexus, injective]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
Expand Down
2 changes: 1 addition & 1 deletion CUSTOMIZE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The logo images you should change are:

- `./src/images/logos/app-logo.svg`
- `./src/images/logos/app-name.svg`
- `./src/images/logos/app-title.png`
- `./src/images/logos/app-title.svg`

These are images are primarily used in the header and footer files:

Expand Down
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": "3.5.1",
"version": "3.6.1",
"author": "J M Rossy",
"dependencies": {
"@chakra-ui/next-js": "^2.1.5",
Expand All @@ -16,8 +16,8 @@
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.14",
"@hyperlane-xyz/sdk": "^3.5.1",
"@hyperlane-xyz/utils": "^3.5.1",
"@hyperlane-xyz/sdk": "^3.6.1",
"@hyperlane-xyz/utils": "^3.6.1",
"@hyperlane-xyz/widgets": "^3.1.4",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@rainbow-me/rainbowkit": "1.3.0",
Expand Down Expand Up @@ -89,10 +89,12 @@
},
"types": "dist/src/index.d.ts",
"resolutions": {
"ethers": "^5.7",
"zustand": "^4.4",
"axios": "0.27.2",
"bn.js": "^5.2",
"cosmjs-types": "0.9",
"ethers": "^5.7",
"lit-html": "2.8.0",
"viem": "1.20.0",
"lit-html": "2.8.0"
"zustand": "^4.4"
}
}
2 changes: 1 addition & 1 deletion public/browserconfig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#025aa1</TileColor>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
Binary file removed public/icon.png
Binary file not shown.
Binary file removed public/logo-with-text.png
Binary file not shown.
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/logo.svg

This file was deleted.

3 changes: 2 additions & 1 deletion src/components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Head from 'next/head';
import Image from 'next/image';
import { PropsWithChildren } from 'react';

import { APP_NAME } from '../../consts/app';
import Planet1 from '../../images/planets/planet-1.webp';
import Planet2 from '../../images/planets/planet-2.webp';
import { Footer } from '../nav/Footer';
Expand All @@ -13,7 +14,7 @@ export function AppLayout({ children }: PropsWithChildren) {
<Head>
{/* https://nextjs.org/docs/messages/no-document-viewport-meta */}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hyperlane Nexus Bridge</title>
<title>{APP_NAME}</title>
</Head>
<div
style={styles.container}
Expand Down
2 changes: 1 addition & 1 deletion src/components/nav/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@ export function Footer() {
}

const styles = {
linkCol: 'flex flex-col gap-2',
linkCol: 'flex flex-col gap-1.5',
linkItem: 'flex items-center capitalize text-decoration-none hover:underline underline-offset-2',
};
1 change: 1 addition & 0 deletions src/consts/app.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export const APP_NAME = 'Hyperlane Warp UI Template';
export const APP_DESCRIPTION = 'A DApp for Hyperlane Warp Route transfers';
export const APP_URL = 'hyperlane-warp-template.vercel.app';
export const APP_BRAND_COLOR = '#025aa1';
4 changes: 2 additions & 2 deletions src/consts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const withdrawalWhitelist = process?.env?.NEXT_PUBLIC_BLOCK_WITHDRAWAL_WHITELIST
const transferBlacklist = process?.env?.NEXT_PUBLIC_TRANSFER_BLACKLIST || '';

interface Config {
debug: boolean; // Enables some debug features in the app
isDevMode: boolean; // Enables some debug features in the app
version: string; // Matches version number in package.json
explorerApiKeys: Record<string, string>; // Optional map of API keys for block explorer
showTipBox: boolean; // Show/Hide the blue tip box above the transfer form
Expand All @@ -21,7 +21,7 @@ interface Config {
}

export const config: Config = Object.freeze({
debug: isDevMode,
isDevMode,
version,
explorerApiKeys,
showTipBox: true,
Expand Down
11 changes: 11 additions & 0 deletions src/consts/igpQuotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ProtocolType } from '@hyperlane-xyz/utils';

// IGP Quote overrides can be set here
// If specified, this value will be used instead of querying the token adapter
// Protocol to value | map<chainId,value>
export const DEFAULT_IGP_QUOTES: Partial<
Record<ProtocolType, string | Record<string | number, string>>
> = {
[ProtocolType.Sealevel]: '10000',
[ProtocolType.Cosmos]: '270000',
};
2 changes: 0 additions & 2 deletions src/consts/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ export const SOL_ZERO_ADDRESS = '00000000000000000000000000000000000000000000';
export const COSMOS_ZERO_ADDRESS = 'cosmos100000000000000000000000000000000000000';
// Strangely, this is not included in any of the Solana packages
export const SOL_SPL_NOOP_ADDRESS = 'noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV';
export const SOL_IGP_QUOTE = '10000';
export const COSM_IGP_QUOTE = '270000';
// Placeholder chain for use with cosmos-kit wallet hooks that require a chain name
export const PLACEHOLDER_COSMOS_CHAIN = 'cosmoshub';
1 change: 1 addition & 0 deletions src/features/chains/cosmosDefault.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const cosmosDefaultChain: ChainMetadata = {
domainId: 1234, // TODO
bech32Prefix: 'cosmos',
slip44: 118,
grpcUrls: [{ http: 'grpc-cosmoshub-ia.cosmosia.notional.ventures:443' }],
rpcUrls: [{ http: 'https://rpc-cosmoshub.blockapsis.com' }],
restUrls: [{ http: 'https://lcd-cosmoshub.blockapsis.com' }],
nativeToken: {
Expand Down
2 changes: 1 addition & 1 deletion src/features/chains/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function getChainDisplayName(id: ChainCaip2Id, shortName = false) {
const metadata = getMultiProvider().tryGetChainMetadata(reference || 0);
if (!metadata) return 'Unknown';
const displayName = shortName ? metadata.displayNameShort : metadata.displayName;
return toTitleCase(displayName || metadata.displayName || metadata.name);
return displayName || metadata.displayName || toTitleCase(metadata.name);
}

export function isPermissionlessChain(id: ChainCaip2Id) {
Expand Down
2 changes: 1 addition & 1 deletion src/features/tokens/AdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export class AdapterFactory {
token: convertToProtocolAddress(baseTokenAddress, protocol, bech32Prefix),
warpRouter: convertToProtocolAddress(routerAddress, protocol, bech32Prefix),
},
tokenMetadata.igpTokenAddress || baseTokenAddress,
tokenMetadata.igpTokenAddressOrDenom || baseTokenAddress,
);
} else {
throw new Error(`Unsupported protocol: ${protocol}`);
Expand Down
4 changes: 2 additions & 2 deletions src/features/tokens/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const commonTokenFields = z.object({
symbol: z.string().optional(),
decimals: z.number().nonnegative().optional(), // decimals == 0 for NFTs
logoURI: z.string().optional(),
igpTokenAddress: z.string().optional(),
igpTokenAddressOrDenom: z.string().optional(),
});
type CommonTokenFields = z.infer<typeof commonTokenFields>;

Expand Down Expand Up @@ -91,7 +91,7 @@ interface BaseTokenMetadata extends MinimalTokenMetadata {
type: ExtendedTokenType;
tokenCaip19Id: TokenCaip19Id;
routerAddress: Address; // Shared name for hypCollateralAddr or hypNativeAddr
igpTokenAddress?: Address;
igpTokenAddressOrDenom?: Address;
logoURI?: string;
}

Expand Down
29 changes: 15 additions & 14 deletions src/features/transfer/useIgpQuote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import { useEffect } from 'react';

import { IHypTokenAdapter } from '@hyperlane-xyz/sdk';
import { ProtocolType, fromWei } from '@hyperlane-xyz/utils';
import { ProtocolType, fromWei, isAddress } from '@hyperlane-xyz/utils';

import { useToastError } from '../../components/toast/useToastError';
import { COSM_IGP_QUOTE, SOL_IGP_QUOTE } from '../../consts/values';
import { getChainReference, getProtocolType } from '../caip/chains';
import { DEFAULT_IGP_QUOTES } from '../../consts/igpQuotes';
import { getChainReference, parseCaip2Id } from '../caip/chains';
import { AssetNamespace, getCaip19Id, getNativeTokenAddress } from '../caip/tokens';
import { getChainMetadata, getMultiProvider } from '../multiProvider';
import { Route } from '../routes/types';
Expand All @@ -22,11 +22,6 @@ import { findTokensByAddress, getToken } from '../tokens/metadata';

import { IgpQuote, IgpTokenType } from './types';

const DEFAULT_IGP_QUOTES = {
[ProtocolType.Sealevel]: SOL_IGP_QUOTE,
[ProtocolType.Cosmos]: COSM_IGP_QUOTE,
};

export function useIgpQuote(route?: Route) {
const setIgpQuote = useStore((state) => state.setIgpQuote);

Expand All @@ -49,14 +44,16 @@ export function useIgpQuote(route?: Route) {

export async function fetchIgpQuote(route: Route, adapter?: IHypTokenAdapter): Promise<IgpQuote> {
const { baseTokenCaip19Id, originCaip2Id, destCaip2Id: destinationCaip2Id } = route;
const originProtocol = getProtocolType(originCaip2Id);
const { protocol: originProtocol, reference: originChainId } = parseCaip2Id(originCaip2Id);
const baseToken = getToken(baseTokenCaip19Id);
if (!baseToken) throw new Error(`No base token found for ${baseTokenCaip19Id}`);

let weiAmount: string;
if (DEFAULT_IGP_QUOTES[originProtocol]) {
// If a default is set for the origin protocol, use that
weiAmount = DEFAULT_IGP_QUOTES[originProtocol];
const defaultQuotes = DEFAULT_IGP_QUOTES[originProtocol];
if (typeof defaultQuotes === 'string') {
weiAmount = defaultQuotes;
} else if (defaultQuotes?.[originChainId]) {
weiAmount = defaultQuotes[originChainId];
} else {
// Otherwise, compute IGP quote via the adapter
adapter ||= AdapterFactory.HypTokenAdapterFromRouteOrigin(route);
Expand All @@ -73,9 +70,13 @@ export async function fetchIgpQuote(route: Route, adapter?: IHypTokenAdapter): P
let tokenDecimals: number;
// If the token has an explicit IGP token address set, use that
// Custom igpTokenAddress configs are supported only from the base (i.e. collateral) token is supported atm
if (isRouteFromBase && baseToken.igpTokenAddress) {
if (
isRouteFromBase &&
baseToken.igpTokenAddressOrDenom &&
isAddress(baseToken.igpTokenAddressOrDenom)
) {
type = IgpTokenType.TokenSeparate;
const igpToken = findTokensByAddress(baseToken.igpTokenAddress)[0];
const igpToken = findTokensByAddress(baseToken.igpTokenAddressOrDenom)[0];
tokenCaip19Id = igpToken.tokenCaip19Id;
// Note this assumes the u prefix because only cosmos tokens use this case
tokenSymbol = igpToken.symbol;
Expand Down
10 changes: 5 additions & 5 deletions src/features/transfer/useTokenTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { ProtocolType, toWei } from '@hyperlane-xyz/utils';

import { toastTxSuccess } from '../../components/toast/TxSuccessToast';
import { COSM_IGP_QUOTE } from '../../consts/values';
import { logger } from '../../utils/logger';
import { parseCaip2Id } from '../caip/chains';
import { isNonFungibleToken } from '../caip/tokens';
Expand Down Expand Up @@ -179,7 +178,7 @@ async function executeTransfer({
logger.debug('Transfer transaction confirmed, hash:', transferTxHash);
toastTxSuccess('Remote transfer started!', transferTxHash, originCaip2Id);
} catch (error) {
logger.error(`Error at stage ${status} `, error);
logger.error(`Error at stage ${status}`, error);
updateTransferStatus(transferIndex, TransferStatus.Failed);
if (JSON.stringify(error).includes('ChainMismatchError')) {
// Wagmi switchNetwork call helps prevent this but isn't foolproof
Expand Down Expand Up @@ -399,6 +398,7 @@ async function executeIbcTransfer({
};

let adapter: IHypTokenAdapter;
let txValue: string | undefined = undefined;
if (isIbcOnlyRoute(tokenRoute)) {
adapter = new CosmIbcTokenAdapter(chainName, multiProvider, {}, adapterProperties);
} else {
Expand All @@ -416,16 +416,16 @@ async function executeIbcTransfer({
intermediateChainName,
},
);
const igpQuote = await fetchIgpQuote(tokenRoute, adapter);
txValue = igpQuote.weiAmount;
}

const transferTxRequest = (await adapter.populateTransferRemoteTx({
weiAmountOrId,
recipient: recipientAddress,
fromAccountOwner: activeAccountAddress,
destination: destinationDomainId,
// TODO have this use fetchIgpQuote?
// Will be required if/when cosmos uses dynamic IGP fees
txValue: COSM_IGP_QUOTE,
txValue,
})) as MsgTransferEncodeObject;

updateStatus(TransferStatus.SigningTransfer);
Expand Down
31 changes: 12 additions & 19 deletions src/pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Head, Html, Main, NextScript } from 'next/document';

import { APP_BRAND_COLOR, APP_DESCRIPTION, APP_NAME, APP_URL } from '../consts/app';

export default function Document() {
return (
<Html>
Expand All @@ -10,33 +12,24 @@ export default function Document() {
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#025aa1" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color={APP_BRAND_COLOR} />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="msapplication-TileColor" content="#025aa1" />
<meta name="msapplication-TileColor" content="#ffffff" />
<meta name="theme-color" content="#ffffff" />

<meta name="application-name" content="Hyperlane Warp Template UI" />
<meta
name="keywords"
content="Hyperlane Warp Route Template UI Token Bridge Interchain App"
/>
<meta
name="description"
content="A web app template for building Hyperlane Warp Route UIs"
/>
<meta name="application-name" content={APP_NAME} />
<meta name="keywords" content={APP_NAME + ' Hyperlane Token Bridge Interchain App'} />
<meta name="description" content={APP_DESCRIPTION} />

<meta name="HandheldFriendly" content="true" />
<meta name="apple-mobile-web-app-title" content="Hyperlane Warp Template UI" />
<meta name="apple-mobile-web-app-title" content={APP_NAME} />
<meta name="apple-mobile-web-app-capable" content="yes" />

<meta property="og:url" content="https://hyperlane.xyz" />
<meta property="og:title" content="Hyperlane Warp Template UI" />
<meta property="og:url" content={APP_URL} />
<meta property="og:title" content={APP_NAME} />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://hyperlane.xyz/logo.png" />
<meta
property="og:description"
content="A web app template for building Hyperlane Warp Route UIs"
/>
<meta property="og:image" content={`${APP_URL}/logo.png`} />
<meta property="og:description" content={APP_DESCRIPTION} />
</Head>
<body className="text-black">
<Main />
Expand Down
Loading

0 comments on commit 5f53d38

Please sign in to comment.