Skip to content

Commit

Permalink
feat: SVM route fixes and improvements (#241)
Browse files Browse the repository at this point in the history
* Override core SVM deployments to include the mailbox addr
* Fixes for SVM warp routes
* Upgrade hyperlane packages to latest
* Improve solana chain detection
* Add Backpack, Salmon and Drif Snap SVM wallet options

---------

Co-authored-by: J M Rossy <[email protected]>
  • Loading branch information
daniel-savu and jmrossy authored Sep 17, 2024
1 parent 1f1ec39 commit eb28157
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 108 deletions.
12 changes: 7 additions & 5 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": "5.0.0",
"version": "5.1.0",
"author": "J M Rossy",
"dependencies": {
"@chakra-ui/next-js": "^2.2.0",
Expand All @@ -13,13 +13,14 @@
"@cosmos-kit/keplr": "^2.12.2",
"@cosmos-kit/leap": "^2.12.2",
"@cosmos-kit/react": "^2.18.0",
"@drift-labs/snap-wallet-adapter": "^0.3.0",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.13.0",
"@headlessui/react": "^1.7.14",
"@hyperlane-xyz/registry": "2.4.0",
"@hyperlane-xyz/sdk": "5.1.0",
"@hyperlane-xyz/utils": "5.1.0",
"@hyperlane-xyz/widgets": "5.1.0",
"@hyperlane-xyz/registry": "4.2.0",
"@hyperlane-xyz/sdk": "5.2.0-beta.1",
"@hyperlane-xyz/utils": "5.2.0-beta.1",
"@hyperlane-xyz/widgets": "5.2.0-beta.1",
"@interchain-ui/react": "^1.23.28",
"@metamask/jazzicon": "https://github.com/jmrossy/jazzicon#7a8df28974b4e81129bfbe3cab76308b889032a6",
"@metamask/post-message-stream": "6.1.2",
Expand Down Expand Up @@ -92,6 +93,7 @@
},
"types": "dist/src/index.d.ts",
"resolutions": {
"@solana/web3.js": "^1.78.4",
"axios": "0.27.2",
"bn.js": "^5.2",
"cosmjs-types": "0.9",
Expand Down
19 changes: 19 additions & 0 deletions src/consts/chains.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import {
eclipsemainnet,
eclipsemainnetAddresses,
solanamainnet,
solanamainnetAddresses,
} from '@hyperlane-xyz/registry';
import { ChainMap, ChainMetadata } from '@hyperlane-xyz/sdk';

// A map of chain names to ChainMetadata
// Chains can be defined here, in chains.json, or in chains.yaml
// Chains already in the SDK need not be included here unless you want to override some fields
// Schema here: https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/sdk/src/metadata/chainMetadataTypes.ts
export const chains: ChainMap<ChainMetadata & { mailbox?: Address }> = {
solanamainnet: {
...solanamainnet,
// SVM chains require mailbox addresses for the token adapters
mailbox: solanamainnetAddresses.mailbox,
// Including a convenient rpc override because the Solana public RPC does not allow browser requests from localhost
rpcUrls: process.env.NEXT_PUBLIC_SOLANA_RPC_URL
? [{ http: process.env.NEXT_PUBLIC_SOLANA_RPC_URL }, ...solanamainnet.rpcUrls]
: solanamainnet.rpcUrls,
},
eclipsemainnet: {
...eclipsemainnet,
mailbox: eclipsemainnetAddresses.mailbox,
},
// mycustomchain: {
// protocol: ProtocolType.Ethereum,
// chainId: 123123,
Expand Down
14 changes: 8 additions & 6 deletions src/features/chains/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { CoreChain, CoreChains } from '@hyperlane-xyz/registry';
import { ChainNameOrId } from '@hyperlane-xyz/sdk';
import { ProtocolType, toTitleCase } from '@hyperlane-xyz/utils';

import { getMultiProvider } from '../../context/context';

const ABACUS_WORKS_DEPLOYER_NAME = 'abacus works';

export function getChainDisplayName(chain: ChainName, shortName = false) {
if (!chain) return 'Unknown';
const metadata = tryGetChainMetadata(chain);
Expand All @@ -14,21 +15,22 @@ export function getChainDisplayName(chain: ChainName, shortName = false) {

export function isPermissionlessChain(chain: ChainName) {
if (!chain) return true;
const metadata = tryGetChainMetadata(chain);
return (
getChainMetadata(chain).protocol !== ProtocolType.Ethereum ||
!CoreChains.includes(chain as CoreChain)
metadata?.protocol !== ProtocolType.Ethereum ||
metadata.deployer?.name.trim().toLowerCase() !== ABACUS_WORKS_DEPLOYER_NAME
);
}

export function hasPermissionlessChain(ids: ChainName[]) {
return !ids.every((c) => !isPermissionlessChain(c));
}

export function getChainByRpcEndpoint(endpoint?: string) {
if (!endpoint) return undefined;
export function getChainByRpcUrl(url?: string) {
if (!url) return undefined;
const allMetadata = Object.values(getMultiProvider().metadata);
return allMetadata.find(
(m) => !!m.rpcUrls.find((rpc) => rpc.http.toLowerCase().includes(endpoint.toLowerCase())),
(m) => !!m.rpcUrls.find((rpc) => rpc.http.toLowerCase().includes(url.toLowerCase())),
);
}

Expand Down
8 changes: 5 additions & 3 deletions src/features/transfer/TransferTokenForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,8 @@ function useFormInitialValues(): TransferFormValues {
}, []);
}

const insufficientFundsErrMsg = /insufficient.funds/i;
const insufficientFundsErrMsg = /insufficient.[funds|lamports]/i;
const emptyAccountErrMsg = /AccountNotFound/i;

async function validateForm(
values: TransferFormValues,
Expand All @@ -447,10 +448,11 @@ async function validateForm(
senderPubKey: await senderPubKey,
});
return result;
} catch (error) {
} catch (error: any) {
logger.error('Error validating form', error);
let errorMsg = errorToString(error, 40);
if (insufficientFundsErrMsg.test(errorMsg)) {
const fullError = `${errorMsg} ${error.message}`;
if (insufficientFundsErrMsg.test(fullError) || emptyAccountErrMsg.test(fullError)) {
errorMsg = 'Insufficient funds for gas fees';
}
return { form: errorMsg };
Expand Down
4 changes: 2 additions & 2 deletions src/features/wallet/WalletEnvSelectionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Image from 'next/image';
import { PropsWithChildren } from 'react';

import { ethereum, solana } from '@hyperlane-xyz/registry';
import { ethereum, solanamainnet } from '@hyperlane-xyz/registry';
import { ProtocolType } from '@hyperlane-xyz/utils';

import { ChainLogo } from '../../components/icons/ChainLogo';
Expand Down Expand Up @@ -31,7 +31,7 @@ export function WalletEnvSelectionModal({ isOpen, close }: { isOpen: boolean; cl
<EnvButton
onClick={onClickEnv(ProtocolType.Sealevel)}
subTitle="a Solana"
logoChainId={solana.chainId}
logoChainId={solanamainnet.chainId}
>
Solana
</EnvButton>
Expand Down
8 changes: 7 additions & 1 deletion src/features/wallet/context/SolanaWalletContext.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { SnapWalletAdapter } from '@drift-labs/snap-wallet-adapter';
import { WalletAdapterNetwork, WalletError } from '@solana/wallet-adapter-base';
import { ConnectionProvider, WalletProvider } from '@solana/wallet-adapter-react';
import { WalletModalProvider } from '@solana/wallet-adapter-react-ui';
import '@solana/wallet-adapter-react-ui/styles.css';
import {
BackpackWalletAdapter,
LedgerWalletAdapter,
PhantomWalletAdapter,
SalmonWalletAdapter,
SolflareWalletAdapter,
TrustWalletAdapter,
} from '@solana/wallet-adapter-wallets';
Expand All @@ -16,12 +19,15 @@ import { logger } from '../../../utils/logger';

export function SolanaWalletContext({ children }: PropsWithChildren<unknown>) {
// TODO support multiple networks
const network = WalletAdapterNetwork.Devnet;
const network = WalletAdapterNetwork.Mainnet;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [
new PhantomWalletAdapter(),
new SolflareWalletAdapter(),
new BackpackWalletAdapter(),
new SalmonWalletAdapter(),
new SnapWalletAdapter(),
new TrustWalletAdapter(),
new LedgerWalletAdapter(),
],
Expand Down
23 changes: 14 additions & 9 deletions src/features/wallet/hooks/solana.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
import { Connection } from '@solana/web3.js';
import { useCallback, useMemo } from 'react';
import { toast } from 'react-toastify';

import { ProviderType, TypedTransactionReceipt, WarpTypedTransaction } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';

import { getMultiProvider } from '../../../context/context';
import { logger } from '../../../utils/logger';
import { getChainByRpcEndpoint } from '../../chains/utils';
import { getChainByRpcUrl } from '../../chains/utils';

import { AccountInfo, ActiveChainInfo, ChainTransactionFns, WalletDetails } from './types';

Expand Down Expand Up @@ -55,20 +54,26 @@ export function useSolActiveChain(): ActiveChainInfo {
const { connection } = useConnection();
const connectionEndpoint = connection?.rpcEndpoint;
return useMemo<ActiveChainInfo>(() => {
const metadata = getChainByRpcEndpoint(connectionEndpoint);
if (!metadata) return {};
return {
chainDisplayName: metadata.displayName,
chainName: metadata.name,
};
try {
const hostname = new URL(connectionEndpoint).hostname;
const metadata = getChainByRpcUrl(hostname);
if (!metadata) return {};
return {
chainDisplayName: metadata.displayName,
chainName: metadata.name,
};
} catch (error) {
logger.warn('Error finding sol active chain', error);
return {};
}
}, [connectionEndpoint]);
}

export function useSolTransactionFns(): ChainTransactionFns {
const { sendTransaction: sendSolTransaction } = useWallet();

const onSwitchNetwork = useCallback(async (chainName: ChainName) => {
toast.warn(`Solana wallet must be connected to origin chain ${chainName}}`);
logger.warn(`Solana wallet must be connected to origin chain ${chainName}`);
}, []);

const onSendTx = useCallback(
Expand Down
Loading

0 comments on commit eb28157

Please sign in to comment.