From 0294a617f6c8d76850cea74bf68edc919187cd8a Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 10 Aug 2023 11:42:17 -0400 Subject: [PATCH 01/24] Make name, symbol, decimals optional in config Check collateral contract balance before transfer Add option to hide disabled tokens in modal Tweak sidebar styles --- src/consts/config.ts | 2 + src/consts/tokens.ts | 60 ++++++------ src/features/tokens/TokenListModal.tsx | 8 +- .../tokens/adapters/EvmTokenAdapter.ts | 25 +++-- src/features/tokens/adapters/ITokenAdapter.ts | 2 +- .../tokens/adapters/SealevelTokenAdapter.ts | 12 +-- src/features/tokens/metadata.ts | 97 +++++++++++++++---- src/features/tokens/routes/hooks.ts | 53 ++-------- src/features/tokens/types.ts | 11 ++- src/features/transfer/useTokenTransfer.ts | 33 +++++-- src/features/wallet/SideBarMenu.tsx | 6 +- src/features/wallet/hooks.tsx | 5 +- 12 files changed, 181 insertions(+), 133 deletions(-) diff --git a/src/consts/config.ts b/src/consts/config.ts index a4ed2a76..d7d37d64 100644 --- a/src/consts/config.ts +++ b/src/consts/config.ts @@ -8,6 +8,7 @@ interface Config { version: string | null; // Matches version number in package.json explorerApiKeys: Record; // Optional map of API keys for block explorer showTipBox: boolean; // Show/Hide the blue tip box above the main form + showDisabledTokens: boolean; // Show/Hide invalid token options in the selection modal walletConnectProjectId: string; } @@ -16,5 +17,6 @@ export const config: Config = Object.freeze({ version, explorerApiKeys, showTipBox: true, + showDisabledTokens: true, walletConnectProjectId, }); diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index 75dc7804..cd2317a4 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -14,27 +14,27 @@ export const tokenList: WarpTokenConfig = [ }, // Example NFT (ERC721) token for an EVM chain - { - chainId: 5, - name: 'Test721', - symbol: 'TEST721', - decimals: 0, - type: 'collateral', - address: '0x77566D540d1E207dFf8DA205ed78750F9a1e7c55', - hypCollateralAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', - isNft: true, - }, + // { + // chainId: 5, + // name: 'Test721', + // symbol: 'TEST721', + // decimals: 0, + // type: 'collateral', + // address: '0x77566D540d1E207dFf8DA205ed78750F9a1e7c55', + // hypCollateralAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + // isNft: true, + // }, // Example native token for an EVM chain - { - type: 'native', - chainId: 11155111, - name: 'Ether', - symbol: 'ETH', - decimals: 18, - hypNativeAddress: '0xEa44A29da87B5464774978e6A4F4072A4c048949', - logoURI: '/logos/weth.png', - }, + // { + // type: 'native', + // chainId: 11155111, + // name: 'Ether', + // symbol: 'ETH', + // decimals: 18, + // hypNativeAddress: '0xEa44A29da87B5464774978e6A4F4072A4c048949', + // logoURI: '/logos/weth.png', + // }, // Example native token for a Sealevel (Solana) chain { @@ -49,15 +49,15 @@ export const tokenList: WarpTokenConfig = [ }, // Example collateral token for a Sealevel (Solana) chain - { - type: 'collateral', - protocol: 'sealevel', - chainId: 1399811151, - address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', - hypCollateralAddress: 'Hsb2PdnUvd7VvZJ1svS8TrVLfsRDdDTWoHK5r2RwGZBS', - name: 'dUSDC', - symbol: 'dUSDC', - decimals: 6, - isSpl2022: false, - }, + // { + // type: 'collateral', + // protocol: 'sealevel', + // chainId: 1399811151, + // address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', + // hypCollateralAddress: 'Hsb2PdnUvd7VvZJ1svS8TrVLfsRDdDTWoHK5r2RwGZBS', + // name: 'dUSDC', + // symbol: 'dUSDC', + // decimals: 6, + // isSpl2022: false, + // }, ]; diff --git a/src/features/tokens/TokenListModal.tsx b/src/features/tokens/TokenListModal.tsx index 923cd67a..d21c07c2 100644 --- a/src/features/tokens/TokenListModal.tsx +++ b/src/features/tokens/TokenListModal.tsx @@ -4,11 +4,12 @@ import { useMemo, useState } from 'react'; import { TokenIcon } from '../../components/icons/TokenIcon'; import { TextInput } from '../../components/input/TextField'; import { Modal } from '../../components/layout/Modal'; +import { config } from '../../consts/config'; import InfoIcon from '../../images/icons/info-circle.svg'; import { getAssetNamespace, getTokenAddress, isNativeToken } from '../caip/tokens'; import { getChainDisplayName } from '../chains/utils'; -import { getAllTokens } from './metadata'; +import { getTokens } from './metadata'; import { RoutesMap } from './routes/types'; import { hasTokenRoute } from './routes/utils'; import { TokenMetadata } from './types'; @@ -81,7 +82,7 @@ export function TokenList({ }) { const tokens = useMemo(() => { const q = searchQuery?.trim().toLowerCase(); - return getAllTokens() + return getTokens() .map((t) => { const hasRoute = hasTokenRoute(originCaip2Id, destinationCaip2Id, t.caip19Id, tokenRoutes); return { ...t, disabled: !hasRoute }; @@ -98,7 +99,8 @@ export function TokenList({ t.symbol.toLowerCase().includes(q) || t.caip19Id.toLowerCase().includes(q) ); - }); + }) + .filter((t) => (config.showDisabledTokens ? true : !t.disabled)); }, [searchQuery, originCaip2Id, destinationCaip2Id, tokenRoutes]); return ( diff --git a/src/features/tokens/adapters/EvmTokenAdapter.ts b/src/features/tokens/adapters/EvmTokenAdapter.ts index 25e39bfb..9b9cd46e 100644 --- a/src/features/tokens/adapters/EvmTokenAdapter.ts +++ b/src/features/tokens/adapters/EvmTokenAdapter.ts @@ -46,10 +46,10 @@ export class EvmNativeTokenAdapter implements ITokenAdapter { } async populateTransferTx({ - amountOrId, + weiAmountOrId, recipient, }: TransferParams): Promise { - const value = BigNumber.from(amountOrId); + const value = BigNumber.from(weiAmountOrId); return { value, to: recipient }; } @@ -96,17 +96,17 @@ export class EvmTokenAdapter } override populateApproveTx({ - amountOrId, + weiAmountOrId, recipient, }: TransferParams): Promise { - return this.contract.populateTransaction.approve(recipient, amountOrId); + return this.contract.populateTransaction.approve(recipient, weiAmountOrId); } override populateTransferTx({ - amountOrId, + weiAmountOrId, recipient, }: TransferParams): Promise { - return this.contract.populateTransaction.transfer(recipient, amountOrId); + return this.contract.populateTransaction.transfer(recipient, weiAmountOrId); } } @@ -144,15 +144,20 @@ export class EvmHypSyntheticAdapter } populateTransferRemoteTx({ - amountOrId, + weiAmountOrId, destination, recipient, txValue, }: TransferRemoteParams): Promise { const recipBytes32 = utils.addressToBytes32(addressToByteHexString(recipient)); - return this.contract.populateTransaction.transferRemote(destination, recipBytes32, amountOrId, { - value: txValue, - }); + return this.contract.populateTransaction.transferRemote( + destination, + recipBytes32, + weiAmountOrId, + { + value: txValue, + }, + ); } } diff --git a/src/features/tokens/adapters/ITokenAdapter.ts b/src/features/tokens/adapters/ITokenAdapter.ts index 2a61a444..4df0e6b1 100644 --- a/src/features/tokens/adapters/ITokenAdapter.ts +++ b/src/features/tokens/adapters/ITokenAdapter.ts @@ -1,7 +1,7 @@ import { MinimalTokenMetadata } from '../types'; export interface TransferParams { - amountOrId: string | number; + weiAmountOrId: string | number; recipient: Address; // Solana-specific params diff --git a/src/features/tokens/adapters/SealevelTokenAdapter.ts b/src/features/tokens/adapters/SealevelTokenAdapter.ts index c52c5501..c3ed2ddf 100644 --- a/src/features/tokens/adapters/SealevelTokenAdapter.ts +++ b/src/features/tokens/adapters/SealevelTokenAdapter.ts @@ -54,13 +54,13 @@ export class SealevelNativeTokenAdapter implements ITokenAdapter { throw new Error('Approve not required for native tokens'); } - populateTransferTx({ amountOrId, recipient, fromAccountOwner }: TransferParams): Transaction { + populateTransferTx({ weiAmountOrId, recipient, fromAccountOwner }: TransferParams): Transaction { const fromPubkey = resolveAddress(fromAccountOwner, this.signerAddress); return new Transaction().add( SystemProgram.transfer({ fromPubkey, toPubkey: new PublicKey(recipient), - lamports: new BigNumber(amountOrId).toNumber(), + lamports: new BigNumber(weiAmountOrId).toNumber(), }), ); } @@ -95,7 +95,7 @@ export class SealevelTokenAdapter implements ITokenAdapter { } populateTransferTx({ - amountOrId, + weiAmountOrId, recipient, fromAccountOwner, fromTokenAccount, @@ -107,7 +107,7 @@ export class SealevelTokenAdapter implements ITokenAdapter { new PublicKey(fromTokenAccount), new PublicKey(recipient), fromWalletPubKey, - new BigNumber(amountOrId).toNumber(), + new BigNumber(weiAmountOrId).toNumber(), ), ); } @@ -181,7 +181,7 @@ export abstract class SealevelHypTokenAdapter } async populateTransferRemoteTx({ - amountOrId, + weiAmountOrId, destination, recipient, fromAccountOwner, @@ -202,7 +202,7 @@ export abstract class SealevelHypTokenAdapter data: new TransferRemoteInstruction({ destination_domain: destination, recipient: addressToBytes(recipient), - amount_or_id: new BigNumber(amountOrId).toNumber(), + amount_or_id: new BigNumber(weiAmountOrId).toNumber(), }), }); const serializedData = serialize(TransferRemoteSchema, value); diff --git a/src/features/tokens/metadata.ts b/src/features/tokens/metadata.ts index 9cd27057..87f8c11f 100644 --- a/src/features/tokens/metadata.ts +++ b/src/features/tokens/metadata.ts @@ -5,23 +5,38 @@ import { tokenList } from '../../consts/tokens'; import { logger } from '../../utils/logger'; import { getCaip2Id } from '../caip/chains'; import { getCaip19Id, getNativeTokenAddress, resolveAssetNamespace } from '../caip/tokens'; +import { getMultiProvider } from '../multiProvider'; -import { TokenMetadata, WarpTokenConfig, WarpTokenConfigSchema } from './types'; +import { EvmTokenAdapter } from './adapters/EvmTokenAdapter'; +import { ITokenAdapter } from './adapters/ITokenAdapter'; +import { getHypErc20CollateralContract } from './contracts/evmContracts'; +import { + MinimalTokenMetadata, + TokenMetadata, + WarpTokenConfig, + WarpTokenConfigSchema, +} from './types'; let tokens: TokenMetadata[]; -export function getAllTokens() { - if (!tokens) { - tokens = parseTokenConfigs(tokenList); - } - return tokens; +export function getTokens() { + return tokens || []; } export function getToken(caip19Id: Caip19Id) { - return getAllTokens().find((t) => t.caip19Id === caip19Id); + return getTokens().find((t) => t.caip19Id === caip19Id); +} + +export async function parseTokens() { + if (!tokens) { + tokens = await parseTokenConfigs(tokenList); + } + return tokens; } -function parseTokenConfigs(configList: WarpTokenConfig): TokenMetadata[] { +// Converts the more user-friendly config format into a validated, extended format +// that's easier for the UI to work with +async function parseTokenConfigs(configList: WarpTokenConfig): Promise { const result = WarpTokenConfigSchema.safeParse(configList); if (!result.success) { logger.error('Invalid token config', result.error); @@ -30,19 +45,29 @@ function parseTokenConfigs(configList: WarpTokenConfig): TokenMetadata[] { const parsedConfig = result.data; const tokenMetadata: TokenMetadata[] = []; - for (const token of parsedConfig) { - const { type, chainId, name, symbol, decimals, logoURI } = token; - const protocol = token.protocol || ProtocolType.Ethereum; + for (const config of parsedConfig) { + const { type, chainId, logoURI } = config; + + const protocol = config.protocol || ProtocolType.Ethereum; const caip2Id = getCaip2Id(protocol, chainId); - const isNft = type === TokenType.collateral && token.isNft; - const isSpl2022 = type === TokenType.collateral && token.isSpl2022; - const address = type === TokenType.collateral ? token.address : getNativeTokenAddress(protocol); + const isNative = type == TokenType.native; + const isNft = type === TokenType.collateral && config.isNft; + const isSpl2022 = type === TokenType.collateral && config.isSpl2022; + const address = + type === TokenType.collateral ? config.address : getNativeTokenAddress(protocol); const routerAddress = - type === TokenType.collateral ? token.hypCollateralAddress : token.hypNativeAddress; - const namespace = resolveAssetNamespace(protocol, type == TokenType.native, isNft, isSpl2022); + type === TokenType.collateral ? config.hypCollateralAddress : config.hypNativeAddress; + const namespace = resolveAssetNamespace(protocol, isNative, isNft, isSpl2022); const caip19Id = getCaip19Id(caip2Id, namespace, address); + + const { name, symbol, decimals } = await fetchNameAndDecimals( + config, + protocol, + routerAddress, + isNft, + ); + tokenMetadata.push({ - chainId, name, symbol, decimals, @@ -54,3 +79,41 @@ function parseTokenConfigs(configList: WarpTokenConfig): TokenMetadata[] { } return tokenMetadata; } + +async function fetchNameAndDecimals( + tokenConfig: WarpTokenConfig[number], + protocol: ProtocolType, + routerAddress: Address, + isNft?: boolean, +): Promise { + const { type, chainId, name, symbol, decimals } = tokenConfig; + if (name && symbol && decimals) { + // Already provided in the config + return { name, symbol, decimals }; + } + + const multiProvider = getMultiProvider(); + if (type === TokenType.native) { + // Use the native token config that may be in the chain metadata + const metadata = multiProvider.getChainMetadata(chainId).nativeToken; + if (!metadata) throw new Error('Name, symbol, or decimals is missing for native token'); + return metadata; + } + + if (type === TokenType.collateral) { + // Fetch the data from the contract + let tokenAdapter: ITokenAdapter; + if (protocol === ProtocolType.Ethereum) { + const provider = multiProvider.getProvider(chainId); + const collateralContract = getHypErc20CollateralContract(routerAddress, provider); + const wrappedTokenAddr = await collateralContract.wrappedToken(); + tokenAdapter = new EvmTokenAdapter(provider, wrappedTokenAddr); + } else { + // TODO solana support when hyp tokens have metadata + throw new Error('Name, symbol, and decimals is required for non-EVM token configs'); + } + return tokenAdapter.getMetadata(isNft); + } + + throw new Error(`Unsupported token type ${type}`); +} diff --git a/src/features/tokens/routes/hooks.ts b/src/features/tokens/routes/hooks.ts index 8d08f744..a5da27f3 100644 --- a/src/features/tokens/routes/hooks.ts +++ b/src/features/tokens/routes/hooks.ts @@ -1,11 +1,10 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; -import { TokenType } from '@hyperlane-xyz/hyperlane-token'; import { ProtocolType } from '@hyperlane-xyz/sdk'; import { logger } from '../../../utils/logger'; -import { getCaip2Id, getProtocolType } from '../../caip/chains'; +import { getCaip2Id } from '../../caip/chains'; import { getCaip2FromToken, getCaip19Id, @@ -13,12 +12,9 @@ import { parseCaip19Id, resolveAssetNamespace, } from '../../caip/tokens'; -import { getMultiProvider, getProvider } from '../../multiProvider'; +import { getMultiProvider } from '../../multiProvider'; import { AdapterFactory } from '../adapters/AdapterFactory'; -import { EvmTokenAdapter } from '../adapters/EvmTokenAdapter'; -import { ITokenAdapter } from '../adapters/ITokenAdapter'; -import { getHypErc20CollateralContract } from '../contracts/evmContracts'; -import { getAllTokens } from '../metadata'; +import { getTokens, parseTokens } from '../metadata'; import { TokenMetadata, TokenMetadataWithHypTokens } from '../types'; import { RouteType, RoutesMap } from './types'; @@ -32,10 +28,10 @@ export function useTokenRoutes() { ['token-routes'], async () => { logger.info('Searching for token routes'); + const parsedTokens = await parseTokens(); const tokens: TokenMetadataWithHypTokens[] = []; - for (const token of getAllTokens()) { + for (const token of parsedTokens) { // Consider parallelizing here but concerned about RPC rate limits - await validateTokenMetadata(token); const tokenWithHypTokens = await fetchRemoteHypTokens(token); tokens.push(tokenWithHypTokens); } @@ -47,43 +43,6 @@ export function useTokenRoutes() { return { isLoading, error, tokenRoutes }; } -async function validateTokenMetadata(token: TokenMetadata) { - const { type, caip19Id, symbol, decimals, routerAddress } = token; - const caip2Id = getCaip2FromToken(caip19Id); - const isNft = isNonFungibleToken(caip19Id); - - // Native tokens cannot be queried for metadata - if (type !== TokenType.collateral) return; - - logger.info(`Validating token ${symbol} on ${caip2Id}`); - - const protocol = getProtocolType(caip2Id); - let tokenAdapter: ITokenAdapter; - if (protocol === ProtocolType.Ethereum) { - const provider = getProvider(caip2Id); - const collateralContract = getHypErc20CollateralContract(routerAddress, provider); - const wrappedTokenAddr = await collateralContract.wrappedToken(); - tokenAdapter = new EvmTokenAdapter(provider, wrappedTokenAddr); - } else if (protocol === ProtocolType.Sealevel) { - // TODO solana support when hyp tokens have metadata - return; - } - - const { decimals: decimalsOnChain, symbol: symbolOnChain } = await tokenAdapter!.getMetadata( - isNft, - ); - if (decimals !== decimalsOnChain) { - throw new Error( - `Token config decimals ${decimals} does not match contract decimals ${decimalsOnChain}`, - ); - } - if (symbol !== symbolOnChain) { - throw new Error( - `Token config symbol ${symbol} does not match contract decimals ${symbolOnChain}`, - ); - } -} - async function fetchRemoteHypTokens( originToken: TokenMetadata, ): Promise { @@ -186,7 +145,7 @@ function getChainsFromTokens(tokens: TokenMetadataWithHypTokens[]): Caip2Id[] { export function useRouteChains(tokenRoutes: RoutesMap): Caip2Id[] { return useMemo(() => { const allCaip2Ids = Object.keys(tokenRoutes) as Caip2Id[]; - const collateralCaip2Ids = getAllTokens().map((t) => getCaip2FromToken(t.caip19Id)); + const collateralCaip2Ids = getTokens().map((t) => getCaip2FromToken(t.caip19Id)); return allCaip2Ids.sort((c1, c2) => { // Surface collateral chains first if (collateralCaip2Ids.includes(c1) && !collateralCaip2Ids.includes(c2)) return -1; diff --git a/src/features/tokens/types.ts b/src/features/tokens/types.ts index 53040a31..c4a08284 100644 --- a/src/features/tokens/types.ts +++ b/src/features/tokens/types.ts @@ -6,11 +6,11 @@ import { ProtocolType } from '@hyperlane-xyz/sdk'; export type MinimalTokenMetadata = Omit; const commonTokenFields = z.object({ - chainId: z.number().positive().or(z.string().nonempty()), + chainId: z.number().positive().or(z.string()), protocol: z.nativeEnum(ProtocolType).optional(), - name: z.string().nonempty(), - symbol: z.string().nonempty(), - decimals: z.number().nonnegative(), // decimals == 0 for NFTs + name: z.string().optional(), + symbol: z.string().optional(), + decimals: z.number().nonnegative().optional(), // decimals == 0 for NFTs logoURI: z.string().optional(), }); type CommonTokenFields = z.infer; @@ -68,10 +68,11 @@ export type WarpTokenConfig = Array; * * See src/features/tokens/metadata.ts */ -interface BaseTokenMetadata extends CommonTokenFields { +interface BaseTokenMetadata extends MinimalTokenMetadata { type: TokenType; caip19Id: Caip19Id; routerAddress: Address; // Shared name for hypCollateralAddr or hypNativeAddr + logoURI?: string; } interface CollateralTokenMetadata extends BaseTokenMetadata { diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 19b3b7e6..94cca4ba 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -115,7 +115,7 @@ async function executeTransfer({ if (!tokenRoute) throw new Error('No token route found between chains'); const isNft = isNonFungibleToken(tokenCaip19Id); - const amountOrId = isNft ? amount : toWei(amount, tokenRoute.decimals).toString(); + const weiAmountOrId = isNft ? amount : toWei(amount, tokenRoute.decimals).toString(); const activeAccountAddress = activeAccounts.accounts[originProtocol]?.address || ''; addTransfer({ @@ -126,10 +126,12 @@ async function executeTransfer({ params: values, }); + await ensureSufficientCollateral(tokenRoute, weiAmountOrId, isNft); + const hypTokenAdapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(tokenRoute); const triggerParams: ExecuteTransferParams = { - amountOrId, + weiAmountOrId, destinationDomainId, recipientAddress, tokenRoute, @@ -177,8 +179,21 @@ async function executeTransfer({ if (onDone) onDone(); } +// In certain cases, like when a synthetic token has >1 collateral tokens +// it's possible that the collateral contract balance is insufficient to +// cover the remote transfer. This ensures the balance is sufficient or throws. +async function ensureSufficientCollateral(route: Route, weiAmount: string, isNft?: boolean) { + if (route.type !== RouteType.SyntheticToBase || isNft) return; + const adapter = AdapterFactory.TokenAdapterFromAddress(route.baseCaip19Id); + logger.debug('Checking collateral balance for token', route.baseCaip19Id); + const balance = await adapter.getBalance(route.baseRouterAddress); + if (BigNumber.from(balance).lt(weiAmount)) { + throw new Error('Collateral contract has insufficient balance'); + } +} + interface ExecuteTransferParams { - amountOrId: string; + weiAmountOrId: string; destinationDomainId: DomainId; recipientAddress: Address; tokenRoute: Route; @@ -191,7 +206,7 @@ interface ExecuteTransferParams { } async function executeEvmTransfer({ - amountOrId, + weiAmountOrId, destinationDomainId, recipientAddress, tokenRoute, @@ -206,7 +221,7 @@ async function executeEvmTransfer({ updateStatus(TransferStatus.CreatingApprove); const tokenAdapter = AdapterFactory.TokenAdapterFromAddress(baseCaip19Id); const approveTxRequest = (await tokenAdapter.populateApproveTx({ - amountOrId, + weiAmountOrId, recipient: baseRouterAddress, })) as EvmTransaction; @@ -230,10 +245,10 @@ async function executeEvmTransfer({ // If sending native tokens (e.g. Eth), the gasPayment must be added to the tx value and sent together const txValue = routeType === RouteType.BaseToSynthetic && isNativeToken(baseCaip19Id) - ? BigNumber.from(gasPayment).add(amountOrId) + ? BigNumber.from(gasPayment).add(weiAmountOrId) : gasPayment; const transferTxRequest = (await hypTokenAdapter.populateTransferRemoteTx({ - amountOrId, + weiAmountOrId, recipient: recipientAddress, destination: destinationDomainId, txValue: txValue.toString(), @@ -254,7 +269,7 @@ async function executeEvmTransfer({ } async function executeSealevelTransfer({ - amountOrId, + weiAmountOrId, destinationDomainId, recipientAddress, tokenRoute, @@ -274,7 +289,7 @@ async function executeSealevelTransfer({ // logger.debug('Quoted gas payment', gasPayment); const transferTxRequest = (await hypTokenAdapter.populateTransferRemoteTx({ - amountOrId, + weiAmountOrId, destination: destinationDomainId, recipient: recipientAddress, fromAccountOwner: activeAccount.address, diff --git a/src/features/wallet/SideBarMenu.tsx b/src/features/wallet/SideBarMenu.tsx index f3f7d505..80025ee8 100644 --- a/src/features/wallet/SideBarMenu.tsx +++ b/src/features/wallet/SideBarMenu.tsx @@ -111,10 +111,10 @@ export function SideBarMenu({ )}
-
+
Connected Wallets
-
+
{readyAccounts.map((a) => (
-
+
Transfer History
diff --git a/src/features/wallet/hooks.tsx b/src/features/wallet/hooks.tsx index e4ad5c2e..5fb3fdab 100644 --- a/src/features/wallet/hooks.tsx +++ b/src/features/wallet/hooks.tsx @@ -242,12 +242,12 @@ export function useTransactionFns(): Record< }) => { if (activeCap2Id && activeCap2Id !== caip2Id) await onSwitchEvmNetwork(caip2Id); const chainId = getEthereumChainId(caip2Id); - const result = await sendEvmTransaction({ + logger.debug(`Sending tx on chain ${caip2Id}`); + const { hash, wait } = await sendEvmTransaction({ chainId, request: tx as providers.TransactionRequest, mode: 'recklesslyUnprepared', }); - const { hash, wait } = result; return { hash, confirm: () => wait(1) }; }, [onSwitchEvmNetwork], @@ -278,6 +278,7 @@ export function useTransactionFns(): Record< value: { blockhash, lastValidBlockHeight }, } = await connection.getLatestBlockhashAndContext(); + logger.debug(`Sending tx on chain ${caip2Id}`); const signature = await sendSolTransaction(tx, connection, { minContextSlot }); const confirm = () => From eaf37930f144cc81fe743a3936031babe91cc847 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 10 Aug 2023 11:44:35 -0400 Subject: [PATCH 02/24] Revert token config changes --- src/consts/tokens.ts | 60 ++++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index cd2317a4..75dc7804 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -14,27 +14,27 @@ export const tokenList: WarpTokenConfig = [ }, // Example NFT (ERC721) token for an EVM chain - // { - // chainId: 5, - // name: 'Test721', - // symbol: 'TEST721', - // decimals: 0, - // type: 'collateral', - // address: '0x77566D540d1E207dFf8DA205ed78750F9a1e7c55', - // hypCollateralAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', - // isNft: true, - // }, + { + chainId: 5, + name: 'Test721', + symbol: 'TEST721', + decimals: 0, + type: 'collateral', + address: '0x77566D540d1E207dFf8DA205ed78750F9a1e7c55', + hypCollateralAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + isNft: true, + }, // Example native token for an EVM chain - // { - // type: 'native', - // chainId: 11155111, - // name: 'Ether', - // symbol: 'ETH', - // decimals: 18, - // hypNativeAddress: '0xEa44A29da87B5464774978e6A4F4072A4c048949', - // logoURI: '/logos/weth.png', - // }, + { + type: 'native', + chainId: 11155111, + name: 'Ether', + symbol: 'ETH', + decimals: 18, + hypNativeAddress: '0xEa44A29da87B5464774978e6A4F4072A4c048949', + logoURI: '/logos/weth.png', + }, // Example native token for a Sealevel (Solana) chain { @@ -49,15 +49,15 @@ export const tokenList: WarpTokenConfig = [ }, // Example collateral token for a Sealevel (Solana) chain - // { - // type: 'collateral', - // protocol: 'sealevel', - // chainId: 1399811151, - // address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', - // hypCollateralAddress: 'Hsb2PdnUvd7VvZJ1svS8TrVLfsRDdDTWoHK5r2RwGZBS', - // name: 'dUSDC', - // symbol: 'dUSDC', - // decimals: 6, - // isSpl2022: false, - // }, + { + type: 'collateral', + protocol: 'sealevel', + chainId: 1399811151, + address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', + hypCollateralAddress: 'Hsb2PdnUvd7VvZJ1svS8TrVLfsRDdDTWoHK5r2RwGZBS', + name: 'dUSDC', + symbol: 'dUSDC', + decimals: 6, + isSpl2022: false, + }, ]; From efe3bec63562a33c92e5013315f694f8e15b3b71 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 13 Aug 2023 09:42:12 -0400 Subject: [PATCH 03/24] Use chain metadata for token protocol --- src/features/tokens/metadata.ts | 3 ++- src/features/tokens/types.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/tokens/metadata.ts b/src/features/tokens/metadata.ts index 87f8c11f..4cc772be 100644 --- a/src/features/tokens/metadata.ts +++ b/src/features/tokens/metadata.ts @@ -42,13 +42,14 @@ async function parseTokenConfigs(configList: WarpTokenConfig): Promise; const commonTokenFields = z.object({ chainId: z.number().positive().or(z.string()), - protocol: z.nativeEnum(ProtocolType).optional(), name: z.string().optional(), symbol: z.string().optional(), decimals: z.number().nonnegative().optional(), // decimals == 0 for NFTs From 940a21103dcf8324e4f6645cd5819033748250a4 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 13 Aug 2023 10:44:10 -0400 Subject: [PATCH 04/24] Update tokendata schema Trim decimals for large numbers --- .../tokens/contracts/sealevelSerialization.ts | 26 +++++++++++++++++++ src/utils/amount.ts | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/features/tokens/contracts/sealevelSerialization.ts b/src/features/tokens/contracts/sealevelSerialization.ts index 6f672b23..af376c40 100644 --- a/src/features/tokens/contracts/sealevelSerialization.ts +++ b/src/features/tokens/contracts/sealevelSerialization.ts @@ -34,6 +34,14 @@ export class HyperlaneTokenData { /// The interchain security module. interchain_security_module?: Uint8Array; interchain_security_module_pubkey?: PublicKey; + // The interchain gas paymaster + interchain_gas_paymaster?: { + address: Uint8Array; + type: number; + }; + interchain_gas_paymaster_pubkey?: PublicKey; + // Gas amounts by destination + destination_gas?: Map; /// Remote routers. remote_routers?: Map; remote_router_pubkeys: Map; @@ -45,6 +53,9 @@ export class HyperlaneTokenData { this.interchain_security_module_pubkey = this.interchain_security_module ? new PublicKey(this.interchain_security_module) : undefined; + this.interchain_gas_paymaster_pubkey = this.interchain_gas_paymaster?.address + ? new PublicKey(this.interchain_gas_paymaster.address) + : undefined; this.remote_router_pubkeys = new Map(); if (this.remote_routers) { for (const [k, v] of this.remote_routers.entries()) { @@ -78,6 +89,21 @@ export const HyperlaneTokenDataSchema = new Map([ ['remote_decimals', 'u8'], ['owner', { kind: 'option', type: [32] }], ['interchain_security_module', { kind: 'option', type: [32] }], + [ + 'interchain_gas_paymaster', + { + kind: 'option', + type: { + kind: 'struct', + fields: [ + ['address', [32]], + ['type', 'u8'], + ], + }, + }, + ], + // ['interchain_gas_paymaster_type', { kind: 'option', type: 'u8' }], + ['destination_gas', { kind: 'map', key: 'u32', value: 'u64' }], ['remote_routers', { kind: 'map', key: 'u32', value: [32] }], ], }, diff --git a/src/utils/amount.ts b/src/utils/amount.ts index b0ecff69..38320057 100644 --- a/src/utils/amount.ts +++ b/src/utils/amount.ts @@ -35,7 +35,8 @@ export function fromWeiRounded( else return MIN_ROUNDED_VALUE.toString(); } - return amount.toFixed(DISPLAY_DECIMALS).toString(); + const displayDecimals = amount.gte(10000) ? 0 : DISPLAY_DECIMALS; + return amount.toFixed(displayDecimals).toString(); } export function toWei( From fc1596b9b0f3837c29e74eb9d2190bb16da1a751 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 13 Aug 2023 10:45:02 -0400 Subject: [PATCH 05/24] Setup Eclipse tokens and chains --- src/consts/chains.ts | 35 +++++++++++++++++---------- src/consts/tokens.ts | 56 +++++++------------------------------------- 2 files changed, 31 insertions(+), 60 deletions(-) diff --git a/src/consts/chains.ts b/src/consts/chains.ts index 9f5c9b6b..b6140afd 100644 --- a/src/consts/chains.ts +++ b/src/consts/chains.ts @@ -1,10 +1,5 @@ -import { ChainMap, ChainMetadataWithArtifacts } from '@hyperlane-xyz/sdk'; -import { - solana, - solanadevnet, - solanatestnet, - zbctestnet, -} from '@hyperlane-xyz/sdk/dist/consts/chainMetadata'; +import { ChainMap, ChainMetadataWithArtifacts, ProtocolType } from '@hyperlane-xyz/sdk'; +import { solana, solanadevnet, solanatestnet } from '@hyperlane-xyz/sdk/dist/consts/chainMetadata'; // A map of chain names to ChainMetadata export const chains: ChainMap = { @@ -53,11 +48,25 @@ export const chains: ChainMap = { interchainGasPaymaster: '', validatorAnnounce: '', }, - zbctestnet: { - ...zbctestnet, - mailbox: '4hW22NXtJ2AXrEVbeAmxjhvxWPSNvfTfAphKXdRBZUco', - interchainGasPaymaster: '', - validatorAnnounce: '', - logoURI: '/logos/zebec.png', + proteustestnet: { + chainId: 88002, + domainId: 88002, + name: 'proteustestnet', + protocol: ProtocolType.Ethereum, + displayName: 'Proteus Testnet', + displayNameShort: 'Proteus', + nativeToken: { + name: 'Zebec', + symbol: 'ZBC', + decimals: 18, + }, + rpcUrls: [ + { + http: 'https://api.proteus.nautchain.xyz/solana', + }, + ], + mailbox: '0x918D3924Fad8F71551D9081172e9Bb169745461e', + interchainGasPaymaster: '0x06b62A9F5AEcc1E601D0E02732b4E1D0705DE7Db', + validatorAnnounce: '0xEEea93d0d0287c71e47B3f62AFB0a92b9E8429a1', }, }; diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index 75dc7804..43606628 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -1,63 +1,25 @@ import { WarpTokenConfig } from '../features/tokens/types'; export const tokenList: WarpTokenConfig = [ - // Example collateral token for an EVM chain + // bsctestnet { type: 'collateral', - chainId: 5, - address: '0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', - hypCollateralAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', - name: 'Weth', - symbol: 'WETH', - decimals: 18, - logoURI: '/logos/weth.png', // See public/logos/ + chainId: 97, + address: '0x64544969ed7ebf5f083679233325356ebe738930', + hypCollateralAddress: '0x31b5234A896FbC4b3e2F7237592D054716762131', }, - // Example NFT (ERC721) token for an EVM chain - { - chainId: 5, - name: 'Test721', - symbol: 'TEST721', - decimals: 0, - type: 'collateral', - address: '0x77566D540d1E207dFf8DA205ed78750F9a1e7c55', - hypCollateralAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', - isNft: true, - }, - - // Example native token for an EVM chain + // proteustestnet { type: 'native', - chainId: 11155111, - name: 'Ether', - symbol: 'ETH', - decimals: 18, - hypNativeAddress: '0xEa44A29da87B5464774978e6A4F4072A4c048949', - logoURI: '/logos/weth.png', + chainId: 88002, + hypNativeAddress: '0x34A9af13c5555BAD0783C220911b9ef59CfDBCEf', }, - // Example native token for a Sealevel (Solana) chain + // solanadevnet { type: 'native', - protocol: 'sealevel', - chainId: 1399811151, - hypNativeAddress: '3s6afZYk3EmjsZQ33N9yPTdSk4cY5CKeQ5wtoBcWjFUn', - name: 'Sol', - symbol: 'SOL', - decimals: 9, - logoURI: '/logos/solana.svg', - }, - - // Example collateral token for a Sealevel (Solana) chain - { - type: 'collateral', - protocol: 'sealevel', chainId: 1399811151, - address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', - hypCollateralAddress: 'Hsb2PdnUvd7VvZJ1svS8TrVLfsRDdDTWoHK5r2RwGZBS', - name: 'dUSDC', - symbol: 'dUSDC', - decimals: 6, - isSpl2022: false, + hypNativeAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', }, ]; From 898bcd6292f5b888dfbd16ebc386f094f435c2f0 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 13 Aug 2023 11:47:24 -0400 Subject: [PATCH 06/24] Fix solanadevnet token config --- src/consts/tokens.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index 43606628..e2014db4 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -18,8 +18,13 @@ export const tokenList: WarpTokenConfig = [ // solanadevnet { - type: 'native', + type: 'collateral', chainId: 1399811151, - hypNativeAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', + hypCollateralAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + name: 'dUSDC', + symbol: 'dUSDC', + decimals: 6, + isSpl2022: false, }, ]; From eb0b2a116f0d6ce59ce270308e595bd626f1d911 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Sun, 13 Aug 2023 15:52:52 -0400 Subject: [PATCH 07/24] Fetch remote router decimal values during init Use origin/remote decimals when checking balances Fix bug with address decoding for evm->sol routers --- .../tokens/adapters/AdapterFactory.ts | 23 ++++-- .../tokens/adapters/EvmTokenAdapter.ts | 20 ++--- src/features/tokens/adapters/ITokenAdapter.ts | 4 +- .../tokens/adapters/SealevelTokenAdapter.ts | 36 ++++++--- src/features/tokens/balances.tsx | 4 +- src/features/tokens/routes/hooks.ts | 76 +++++++++++++------ src/features/tokens/routes/types.ts | 3 +- src/features/tokens/types.ts | 2 +- src/features/transfer/TransferTokenForm.tsx | 4 +- src/features/transfer/useTokenTransfer.ts | 2 +- src/utils/addresses.ts | 10 +++ 11 files changed, 125 insertions(+), 59 deletions(-) diff --git a/src/features/tokens/adapters/AdapterFactory.ts b/src/features/tokens/adapters/AdapterFactory.ts index e27fdf75..d1c530b2 100644 --- a/src/features/tokens/adapters/AdapterFactory.ts +++ b/src/features/tokens/adapters/AdapterFactory.ts @@ -44,14 +44,27 @@ export class AdapterFactory { } } - static HypCollateralAdapterFromAddress(caip19Id: Caip19Id, routerAddress: Address) { - const caip2Id = getCaip2FromToken(caip19Id); + static HypCollateralAdapterFromAddress(baseCaip19Id: Caip19Id, routerAddress: Address) { return AdapterFactory.selectHypAdapter( - caip2Id, + getCaip2FromToken(baseCaip19Id), routerAddress, - caip19Id, + baseCaip19Id, EvmHypCollateralAdapter, - isNativeToken(caip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, + isNativeToken(baseCaip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, + ); + } + + static HypSyntheticTokenAdapterFromAddress( + baseCaip19Id: Caip19Id, + caip2Id: Caip2Id, + routerAddress: Address, + ) { + return AdapterFactory.selectHypAdapter( + caip2Id, + routerAddress, + baseCaip19Id, + EvmHypSyntheticAdapter, + SealevelHypSyntheticAdapter, ); } diff --git a/src/features/tokens/adapters/EvmTokenAdapter.ts b/src/features/tokens/adapters/EvmTokenAdapter.ts index 9b9cd46e..43182162 100644 --- a/src/features/tokens/adapters/EvmTokenAdapter.ts +++ b/src/features/tokens/adapters/EvmTokenAdapter.ts @@ -4,11 +4,7 @@ import { BigNumber, PopulatedTransaction, Signer, providers } from 'ethers'; import type { ERC20Upgradeable, HypERC20 } from '@hyperlane-xyz/hyperlane-token'; import { utils } from '@hyperlane-xyz/utils'; -import { - addressToByteHexString, - isValidEvmAddress, - normalizeEvmAddress, -} from '../../../utils/addresses'; +import { addressToByteHexString, isValidEvmAddress, trimLeading0x } from '../../../utils/addresses'; import { getErc20Contract, getHypErc20CollateralContract, @@ -127,14 +123,20 @@ export class EvmHypSyntheticAdapter return this.contract.domains(); } - async getRouterAddress(domain: DomainId): Promise
{ + async getRouterAddress(domain: DomainId): Promise { const routerAddressesAsBytes32 = await this.contract.routers(domain); - return normalizeEvmAddress(utils.bytes32ToAddress(routerAddressesAsBytes32)); + // Evm addresses will be padded with 12 bytes + if (routerAddressesAsBytes32.startsWith('0x000000000000000000000000')) { + return Buffer.from(trimLeading0x(utils.bytes32ToAddress(routerAddressesAsBytes32)), 'hex'); + // Otherwise leave the address unchanged + } else { + return Buffer.from(trimLeading0x(routerAddressesAsBytes32), 'hex'); + } } - async getAllRouters(): Promise> { + async getAllRouters(): Promise> { const domains = await this.getDomains(); - const routers: Address[] = await Promise.all(domains.map((d) => this.getRouterAddress(d))); + const routers: Buffer[] = await Promise.all(domains.map((d) => this.getRouterAddress(d))); return domains.map((d, i) => ({ domain: d, address: routers[i] })); } diff --git a/src/features/tokens/adapters/ITokenAdapter.ts b/src/features/tokens/adapters/ITokenAdapter.ts index 4df0e6b1..db2e8c56 100644 --- a/src/features/tokens/adapters/ITokenAdapter.ts +++ b/src/features/tokens/adapters/ITokenAdapter.ts @@ -25,8 +25,8 @@ export interface ITokenAdapter { export interface IHypTokenAdapter extends ITokenAdapter { getDomains(): Promise; - getRouterAddress(domain: DomainId): Promise
; - getAllRouters(): Promise>; + getRouterAddress(domain: DomainId): Promise; + getAllRouters(): Promise>; quoteGasPayment(destination: DomainId): Promise; populateTransferRemoteTx(TransferParams: TransferRemoteParams): unknown | Promise; } diff --git a/src/features/tokens/adapters/SealevelTokenAdapter.ts b/src/features/tokens/adapters/SealevelTokenAdapter.ts index c3ed2ddf..ba5cb79d 100644 --- a/src/features/tokens/adapters/SealevelTokenAdapter.ts +++ b/src/features/tokens/adapters/SealevelTokenAdapter.ts @@ -22,6 +22,7 @@ import { addressToBytes, isZeroishAddress } from '../../../utils/addresses'; import { AccountDataWrapper, HypTokenInstruction, + HyperlaneTokenData, HyperlaneTokenDataSchema, TransferRemoteInstruction, TransferRemoteSchema, @@ -147,31 +148,42 @@ export abstract class SealevelHypTokenAdapter this.warpProgramPubKey = new PublicKey(warpRouteProgramId); } + async getTokenAccountData(): Promise { + const tokenPda = this.deriveHypTokenAccount(); + const accountInfo = await this.connection.getAccountInfo(tokenPda); + if (!accountInfo) throw new Error(`No account info found for ${tokenPda}`); + const wrappedData = deserializeUnchecked( + HyperlaneTokenDataSchema, + AccountDataWrapper, + accountInfo.data, + ); + return wrappedData.data; + } + + override async getMetadata(): Promise { + const tokenData = await this.getTokenAccountData(); + // TODO full token metadata support + return { decimals: tokenData.decimals, symbol: 'HYP', name: 'Unknown Hyp Token' }; + } + async getDomains(): Promise { const routers = await this.getAllRouters(); return routers.map((router) => router.domain); } - async getRouterAddress(domain: DomainId): Promise
{ + async getRouterAddress(domain: DomainId): Promise { const routers = await this.getAllRouters(); const addr = routers.find((router) => router.domain === domain)?.address; if (!addr) throw new Error(`No router found for ${domain}`); return addr; } - async getAllRouters(): Promise> { - const tokenPda = this.deriveHypTokenAccount(); - const accountInfo = await this.connection.getAccountInfo(tokenPda); - if (!accountInfo) throw new Error(`No account info found for ${tokenPda}}`); - const tokenData = deserializeUnchecked( - HyperlaneTokenDataSchema, - AccountDataWrapper, - accountInfo.data, - ); - const domainToPubKey = tokenData.data.remote_router_pubkeys; + async getAllRouters(): Promise> { + const tokenData = await this.getTokenAccountData(); + const domainToPubKey = tokenData.remote_router_pubkeys; return Array.from(domainToPubKey.entries()).map(([domain, pubKey]) => ({ domain, - address: pubKey.toBase58(), + address: pubKey.toBuffer(), })); } diff --git a/src/features/tokens/balances.tsx b/src/features/tokens/balances.tsx index 27f54740..8604eb75 100644 --- a/src/features/tokens/balances.tsx +++ b/src/features/tokens/balances.tsx @@ -41,7 +41,7 @@ export function useOriginBalance( if (!route || !address || !isValidAddress(address, protocol)) return null; const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(route); const balance = await adapter.getBalance(address); - return { balance, decimals: route.decimals }; + return { balance, decimals: route.originDecimals }; }, refetchInterval: 5000, }); @@ -76,7 +76,7 @@ export function useDestinationBalance( if (!route || !recipientAddress || !isValidAddress(recipientAddress, protocol)) return null; const adapter = AdapterFactory.HypTokenAdapterFromRouteDest(route); const balance = await adapter.getBalance(recipientAddress); - return { balance, decimals: route.decimals }; + return { balance, decimals: route.destDecimals }; }, refetchInterval: 5000, }); diff --git a/src/features/tokens/routes/hooks.ts b/src/features/tokens/routes/hooks.ts index a5da27f3..a1adc4e3 100644 --- a/src/features/tokens/routes/hooks.ts +++ b/src/features/tokens/routes/hooks.ts @@ -3,6 +3,7 @@ import { useMemo } from 'react'; import { ProtocolType } from '@hyperlane-xyz/sdk'; +import { areAddressesEqual, bytesToProtocolAddress } from '../../../utils/addresses'; import { logger } from '../../../utils/logger'; import { getCaip2Id } from '../../caip/chains'; import { @@ -32,7 +33,7 @@ export function useTokenRoutes() { const tokens: TokenMetadataWithHypTokens[] = []; for (const token of parsedTokens) { // Consider parallelizing here but concerned about RPC rate limits - const tokenWithHypTokens = await fetchRemoteHypTokens(token); + const tokenWithHypTokens = await fetchRemoteHypTokens(token, parsedTokens); tokens.push(tokenWithHypTokens); } return computeTokenRoutes(tokens); @@ -44,26 +45,43 @@ export function useTokenRoutes() { } async function fetchRemoteHypTokens( - originToken: TokenMetadata, + baseToken: TokenMetadata, + allTokens: TokenMetadata[], ): Promise { - const { symbol, caip19Id, routerAddress } = originToken; - const isNft = isNonFungibleToken(caip19Id); - logger.info(`Fetching remote tokens for symbol ${symbol} (${caip19Id})`); + const { symbol: baseSymbol, caip19Id: baseCaip19Id, routerAddress: baseRouter } = baseToken; + const isNft = isNonFungibleToken(baseCaip19Id); + logger.info(`Fetching remote tokens for symbol ${baseSymbol} (${baseCaip19Id})`); - const hypTokenAdapter = AdapterFactory.HypCollateralAdapterFromAddress(caip19Id, routerAddress); + const baseAdapter = AdapterFactory.HypCollateralAdapterFromAddress(baseCaip19Id, baseRouter); - const remoteRouters = await hypTokenAdapter.getAllRouters(); + const remoteRouters = await baseAdapter.getAllRouters(); logger.info(`Router addresses found:`, remoteRouters); const multiProvider = getMultiProvider(); - const hypTokens = remoteRouters.map((router) => { - const destMetadata = multiProvider.getChainMetadata(router.domain); - const protocol = destMetadata.protocol || ProtocolType.Ethereum; - const caip2Id = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); - const namespace = resolveAssetNamespace(protocol, false, isNft, true); - return getCaip19Id(caip2Id, namespace, router.address); - }); - return { ...originToken, hypTokens }; + const hypTokens = await Promise.all( + remoteRouters.map(async (router) => { + const destMetadata = multiProvider.getChainMetadata(router.domain); + const protocol = destMetadata.protocol || ProtocolType.Ethereum; + const caip2Id = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); + const namespace = resolveAssetNamespace(protocol, false, isNft, true); + const formattedAddress = bytesToProtocolAddress(router.address, protocol); + const caip19Id = getCaip19Id(caip2Id, namespace, formattedAddress); + // Attempt to find the decimals from the token list + const routerMetadata = allTokens.find((token) => + areAddressesEqual(formattedAddress, token.routerAddress), + ); + if (routerMetadata) return { caip19Id, decimals: routerMetadata.decimals }; + // Otherwise try to query the contract + const remoteAdapter = AdapterFactory.HypSyntheticTokenAdapterFromAddress( + baseCaip19Id, + caip2Id, + formattedAddress, + ); + const metadata = await remoteAdapter.getMetadata(); + return { caip19Id, decimals: metadata.decimals }; + }), + ); + return { ...baseToken, hypTokens }; } // Process token list to populates routesCache with all possible token routes (e.g. router pairs) @@ -83,10 +101,16 @@ function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { // Compute all possible routes, in both directions for (const token of tokens) { for (const hypToken of token.hypTokens) { - const { caip19Id: baseCaip19Id, routerAddress: baseRouterAddress, decimals } = token; + const { + caip19Id: baseCaip19Id, + routerAddress: baseRouterAddress, + decimals: baseDecimals, + } = token; const baseCaip2Id = getCaip2FromToken(baseCaip19Id); - const { caip2Id: syntheticCaip2Id, address: syntheticRouterAddress } = - parseCaip19Id(hypToken); + const { caip2Id: syntheticCaip2Id, address: syntheticRouterAddress } = parseCaip19Id( + hypToken.caip19Id, + ); + const syntheticDecimals = hypToken.decimals; const commonRouteProps = { baseCaip19Id, @@ -97,33 +121,37 @@ function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { ...commonRouteProps, originCaip2Id: baseCaip2Id, originRouterAddress: baseRouterAddress, + originDecimals: baseDecimals, destCaip2Id: syntheticCaip2Id, destRouterAddress: syntheticRouterAddress, - decimals, + destDecimals: syntheticDecimals, }); tokenRoutes[syntheticCaip2Id][baseCaip2Id]?.push({ type: RouteType.SyntheticToBase, ...commonRouteProps, originCaip2Id: syntheticCaip2Id, originRouterAddress: syntheticRouterAddress, + originDecimals: syntheticDecimals, destCaip2Id: baseCaip2Id, destRouterAddress: baseRouterAddress, - decimals, + destDecimals: baseDecimals, }); for (const otherHypToken of token.hypTokens) { // Skip if it's same hypToken as parent loop (no route to self) if (otherHypToken === hypToken) continue; - const { caip2Id: otherSynCaip2Id, address: otherHypTokenAddress } = - parseCaip19Id(otherHypToken); + const { caip2Id: otherSynCaip2Id, address: otherHypTokenAddress } = parseCaip19Id( + otherHypToken.caip19Id, + ); tokenRoutes[syntheticCaip2Id][otherSynCaip2Id]?.push({ type: RouteType.SyntheticToSynthetic, ...commonRouteProps, originCaip2Id: syntheticCaip2Id, originRouterAddress: syntheticRouterAddress, + originDecimals: syntheticDecimals, destCaip2Id: otherSynCaip2Id, destRouterAddress: otherHypTokenAddress, - decimals, + destDecimals: otherHypToken.decimals, }); } } @@ -136,7 +164,7 @@ function getChainsFromTokens(tokens: TokenMetadataWithHypTokens[]): Caip2Id[] { for (const token of tokens) { chains.add(getCaip2FromToken(token.caip19Id)); for (const hypToken of token.hypTokens) { - chains.add(getCaip2FromToken(hypToken)); + chains.add(getCaip2FromToken(hypToken.caip19Id)); } } return Array.from(chains); diff --git a/src/features/tokens/routes/types.ts b/src/features/tokens/routes/types.ts index 8a22191e..e1d66a64 100644 --- a/src/features/tokens/routes/types.ts +++ b/src/features/tokens/routes/types.ts @@ -10,9 +10,10 @@ export interface Route { baseRouterAddress: Address; originCaip2Id: Caip2Id; originRouterAddress: Address; + originDecimals: number; destCaip2Id: Caip2Id; destRouterAddress: Address; - decimals: number; + destDecimals: number; } export type RoutesMap = Record>; diff --git a/src/features/tokens/types.ts b/src/features/tokens/types.ts index ac3e319e..2bdd907b 100644 --- a/src/features/tokens/types.ts +++ b/src/features/tokens/types.ts @@ -88,7 +88,7 @@ export type TokenMetadata = CollateralTokenMetadata | NativeTokenMetadata; * Extended types including synthetic hyp token addresses */ interface HypTokens { - hypTokens: Array; + hypTokens: Array<{ caip19Id: Caip19Id; decimals: number }>; } type NativeTokenMetadataWithHypTokens = NativeTokenMetadata & HypTokens; diff --git a/src/features/transfer/TransferTokenForm.tsx b/src/features/transfer/TransferTokenForm.tsx index 4c73ca88..08ea52af 100644 --- a/src/features/transfer/TransferTokenForm.tsx +++ b/src/features/transfer/TransferTokenForm.tsx @@ -354,7 +354,7 @@ function ReviewDetails({ visible, tokenRoutes }: { visible: boolean; tokenRoutes const route = getTokenRoute(originCaip2Id, destinationCaip2Id, token, tokenRoutes); const isNft = token && isNonFungibleToken(token); - const sendValue = isNft ? amount.toString() : toWei(amount, route?.decimals).toString(); + const sendValue = isNft ? amount.toString() : toWei(amount, route?.originDecimals).toString(); const isApproveRequired = route && isTransferApproveRequired(route, token); const originProtocol = getProtocolType(originCaip2Id); const originUnitName = ProtocolSmallestUnit[originProtocol]; @@ -416,7 +416,7 @@ function validateFormValues( const parsedAmount = tryParseAmount(amount); if (!parsedAmount || parsedAmount.lte(0)) return { amount: isNft ? 'Invalid Token Id' : 'Invalid amount' }; - const sendValue = isNft ? parsedAmount : toWei(parsedAmount, route?.decimals); + const sendValue = isNft ? parsedAmount : toWei(parsedAmount, route?.originDecimals); if (!isNft) { // Validate balances for ERC20-like tokens diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 94cca4ba..0bcda366 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -115,7 +115,7 @@ async function executeTransfer({ if (!tokenRoute) throw new Error('No token route found between chains'); const isNft = isNonFungibleToken(tokenCaip19Id); - const weiAmountOrId = isNft ? amount : toWei(amount, tokenRoute.decimals).toString(); + const weiAmountOrId = isNft ? amount : toWei(amount, tokenRoute.originDecimals).toString(); const activeAccountAddress = activeAccounts.accounts[originProtocol]?.address || ''; addTransfer({ diff --git a/src/utils/addresses.ts b/src/utils/addresses.ts index 8dd6d4c6..7748cc95 100644 --- a/src/utils/addresses.ts +++ b/src/utils/addresses.ts @@ -192,6 +192,16 @@ export function convertToProtocolAddress(address: string, protocol: ProtocolType } } +export function bytesToProtocolAddress(bytes: Buffer, toProtocol: ProtocolType) { + if (toProtocol === ProtocolType.Sealevel) { + return new PublicKey(bytes).toBase58(); + } else if (toProtocol === ProtocolType.Ethereum) { + return utils.bytes32ToAddress(bytes.toString('hex')); + } else { + throw new Error(`Unsupported protocol for address ${toProtocol}`); + } +} + export function trimLeading0x(input: string) { return input.startsWith('0x') ? input.substring(2) : input; } From a36f1c92ad776b68d3fce784c33633101d122f37 Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Mon, 14 Aug 2023 15:36:41 +0100 Subject: [PATCH 08/24] ZBC symbol, hide routes we don't want to see, fix Solana remote balance checking --- src/consts/config.ts | 4 ++-- src/consts/tokens.ts | 10 ++++++++-- src/features/tokens/balances.tsx | 21 ++++++++++++++++++--- src/features/tokens/routes/utils.ts | 5 ++++- src/features/transfer/useTokenTransfer.ts | 1 + 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/consts/config.ts b/src/consts/config.ts index d7d37d64..029b92a9 100644 --- a/src/consts/config.ts +++ b/src/consts/config.ts @@ -16,7 +16,7 @@ export const config: Config = Object.freeze({ debug: isDevMode, version, explorerApiKeys, - showTipBox: true, - showDisabledTokens: true, + showTipBox: false, + showDisabledTokens: false, walletConnectProjectId, }); diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index e2014db4..4fa734f6 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -7,6 +7,9 @@ export const tokenList: WarpTokenConfig = [ chainId: 97, address: '0x64544969ed7ebf5f083679233325356ebe738930', hypCollateralAddress: '0x31b5234A896FbC4b3e2F7237592D054716762131', + symbol: 'ZBC', + name: 'Zebec', + decimals: 18, }, // proteustestnet @@ -14,6 +17,9 @@ export const tokenList: WarpTokenConfig = [ type: 'native', chainId: 88002, hypNativeAddress: '0x34A9af13c5555BAD0783C220911b9ef59CfDBCEf', + symbol: 'ZBC', + name: 'Zebec', + decimals: 18, }, // solanadevnet @@ -22,8 +28,8 @@ export const tokenList: WarpTokenConfig = [ chainId: 1399811151, address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', hypCollateralAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', - name: 'dUSDC', - symbol: 'dUSDC', + name: 'Zebec', + symbol: 'ZBC', decimals: 6, isSpl2022: false, }, diff --git a/src/features/tokens/balances.tsx b/src/features/tokens/balances.tsx index 8604eb75..b5b09027 100644 --- a/src/features/tokens/balances.tsx +++ b/src/features/tokens/balances.tsx @@ -71,12 +71,27 @@ export function useDestinationBalance( tokenRoutes, ], queryFn: async () => { - const route = getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); + // NOTE: this is a hack to accommodate destination balances, specifically the case + // when the destination is a Sealevel chain and is a non-synthetic warp route. + // This only really works with the specific setup of tokens.ts. + + // This searches for the route where the origin chain is destinationCaip2Id + // and the destination chain is originCaip2Id and where the origin is a base token. + const targetBaseCaip19Id = tokenRoutes[destinationCaip2Id][originCaip2Id].find((r) => + r.baseCaip19Id.startsWith(destinationCaip2Id), + )!.baseCaip19Id; + const route = getTokenRoute( + destinationCaip2Id, + originCaip2Id, + targetBaseCaip19Id, + tokenRoutes, + ); const protocol = getProtocolType(destinationCaip2Id); if (!route || !recipientAddress || !isValidAddress(recipientAddress, protocol)) return null; - const adapter = AdapterFactory.HypTokenAdapterFromRouteDest(route); + + const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(route); const balance = await adapter.getBalance(recipientAddress); - return { balance, decimals: route.destDecimals }; + return { balance, decimals: route.originDecimals }; }, refetchInterval: 5000, }); diff --git a/src/features/tokens/routes/utils.ts b/src/features/tokens/routes/utils.ts index 31dc50e4..ee0773a2 100644 --- a/src/features/tokens/routes/utils.ts +++ b/src/features/tokens/routes/utils.ts @@ -26,5 +26,8 @@ export function hasTokenRoute( caip19Id: Caip19Id, tokenRoutes: RoutesMap, ): boolean { - return !!getTokenRoute(originCaip2Id, destinationCaip2Id, caip19Id, tokenRoutes); + const tokenRoute = getTokenRoute(originCaip2Id, destinationCaip2Id, caip19Id, tokenRoutes); + // This will break things if there are other warp routes configured! + // This only looks for routes in which the origin is the base token. + return !!tokenRoute && caip19Id.startsWith(originCaip2Id); } diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 0bcda366..7ff2c303 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -126,6 +126,7 @@ async function executeTransfer({ params: values, }); + // Come back here await ensureSufficientCollateral(tokenRoute, weiAmountOrId, isNft); const hypTokenAdapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(tokenRoute); From fb7da5a9280d5526d7062d5e7902311081278bdb Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Mon, 14 Aug 2023 19:12:21 +0100 Subject: [PATCH 09/24] Fix check if there's sufficient collateral at the dest --- src/features/transfer/useTokenTransfer.ts | 50 +++++++++++++++++++---- src/utils/amount.ts | 15 +++++++ 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 7ff2c303..4406bf9b 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'; import { HyperlaneCore, ProtocolType } from '@hyperlane-xyz/sdk'; import { toastTxSuccess } from '../../components/toast/TxSuccessToast'; -import { toWei } from '../../utils/amount'; +import { convertDecimals, toWei } from '../../utils/amount'; import { logger } from '../../utils/logger'; import { getProtocolType, parseCaip2Id } from '../caip/chains'; import { isNativeToken, isNonFungibleToken } from '../caip/tokens'; @@ -27,6 +27,8 @@ import { import { TransferContext, TransferFormValues, TransferStatus } from './types'; +const COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR = 'Collateral contract balance insufficient'; + export function useTokenTransfer(onDone?: () => void) { const { transfers, addTransfer, updateTransferStatus } = useStore((s) => ({ transfers: s.transfers, @@ -127,7 +129,7 @@ async function executeTransfer({ }); // Come back here - await ensureSufficientCollateral(tokenRoute, weiAmountOrId, isNft); + await ensureSufficientCollateral(tokenRoutes, tokenRoute, weiAmountOrId, isNft); const hypTokenAdapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(tokenRoute); @@ -183,13 +185,43 @@ async function executeTransfer({ // In certain cases, like when a synthetic token has >1 collateral tokens // it's possible that the collateral contract balance is insufficient to // cover the remote transfer. This ensures the balance is sufficient or throws. -async function ensureSufficientCollateral(route: Route, weiAmount: string, isNft?: boolean) { - if (route.type !== RouteType.SyntheticToBase || isNft) return; - const adapter = AdapterFactory.TokenAdapterFromAddress(route.baseCaip19Id); - logger.debug('Checking collateral balance for token', route.baseCaip19Id); - const balance = await adapter.getBalance(route.baseRouterAddress); - if (BigNumber.from(balance).lt(weiAmount)) { - throw new Error('Collateral contract has insufficient balance'); +async function ensureSufficientCollateral( + tokenRoutes: RoutesMap, + route: Route, + weiAmount: string, + isNft?: boolean, +) { + if (isNft) return; + + // NOTE: this is a hack to accommodate destination balances, specifically the case + // when the destination is a Sealevel chain and is a non-synthetic warp route. + // This only really works with the specific setup of tokens.ts. + + // This searches for the route where the origin chain is destinationCaip2Id + // and the destination chain is originCaip2Id and where the origin is a base token. + const targetBaseCaip19Id = tokenRoutes[route.destCaip2Id][route.originCaip2Id].find((r) => + r.baseCaip19Id.startsWith(route.destCaip2Id), + )!.baseCaip19Id; + const targetRoute = getTokenRoute( + route.destCaip2Id, + route.originCaip2Id, + targetBaseCaip19Id, + tokenRoutes, + ); + if (!targetRoute) return; + + const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(targetRoute); + const destinationBalance = await adapter.getBalance(targetRoute.baseRouterAddress); + + const destinationBalanceInOriginDecimals = convertDecimals( + route.destDecimals, + route.originDecimals, + destinationBalance, + ); + + if (destinationBalanceInOriginDecimals.lt(weiAmount)) { + toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); + throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); } } diff --git a/src/utils/amount.ts b/src/utils/amount.ts index 38320057..3e825975 100644 --- a/src/utils/amount.ts +++ b/src/utils/amount.ts @@ -75,3 +75,18 @@ export function areAmountsNearlyEqual(amountInWei1: BigNumber, amountInWei2: Num // Is difference btwn amount and balance less than min amount shown for token return amountInWei1.minus(amountInWei2).abs().lt(minValueWei); } + +export function convertDecimals(fromDecimals: number, toDecimals: number, value: NumberT) { + const amount = new BigNumber(value); + + if (fromDecimals === toDecimals) return amount; + else if (fromDecimals > toDecimals) { + const difference = fromDecimals - toDecimals; + return amount.div(new BigNumber(10).pow(difference)).integerValue(BigNumber.ROUND_FLOOR); + } + // fromDecimals < toDecimals + else { + const difference = toDecimals - fromDecimals; + return amount.times(new BigNumber(10).pow(difference)); + } +} From 07531fd83b81634fb4f8362aa69c487f49b6fabb Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 14 Aug 2023 15:49:25 -0400 Subject: [PATCH 10/24] Remove Hyperlane explorer link Fix block explorer links Show toast on recipient balance increase Show toast from self button if wallet not connected Improve sidebar menu scrolling --- src/consts/chains.ts | 23 ++- src/features/multiProvider.ts | 24 ++++ src/features/transfer/TransferTokenForm.tsx | 32 ++++- .../transfer/TransfersDetailsModal.tsx | 23 ++- src/features/wallet/SideBarMenu.tsx | 135 +++++++++--------- src/utils/links.ts | 3 + 6 files changed, 163 insertions(+), 77 deletions(-) diff --git a/src/consts/chains.ts b/src/consts/chains.ts index b6140afd..77e91bcb 100644 --- a/src/consts/chains.ts +++ b/src/consts/chains.ts @@ -1,4 +1,9 @@ -import { ChainMap, ChainMetadataWithArtifacts, ProtocolType } from '@hyperlane-xyz/sdk'; +import { + ChainMap, + ChainMetadataWithArtifacts, + ExplorerFamily, + ProtocolType, +} from '@hyperlane-xyz/sdk'; import { solana, solanadevnet, solanatestnet } from '@hyperlane-xyz/sdk/dist/consts/chainMetadata'; // A map of chain names to ChainMetadata @@ -44,6 +49,14 @@ export const chains: ChainMap = { }, solanadevnet: { ...solanadevnet, + blockExplorers: [ + { + name: 'Solana Explorer', + url: 'https://explorer.solana.com', + apiUrl: 'https://explorer.solana.com', + family: ExplorerFamily.Other, + }, + ], mailbox: '4v25Dz9RccqUrTzmfHzJMsjd1iVoNrWzeJ4o6GYuJrVn', interchainGasPaymaster: '', validatorAnnounce: '', @@ -65,6 +78,14 @@ export const chains: ChainMap = { http: 'https://api.proteus.nautchain.xyz/solana', }, ], + blockExplorers: [ + { + name: 'Proteus Explorer', + url: 'https://proteus.nautscan.com/proteus', + apiUrl: 'https://proteus.nautscan.com/proteus', + family: ExplorerFamily.Other, + }, + ], mailbox: '0x918D3924Fad8F71551D9081172e9Bb169745461e', interchainGasPaymaster: '0x06b62A9F5AEcc1E601D0E02732b4E1D0705DE7Db', validatorAnnounce: '0xEEea93d0d0287c71e47B3f62AFB0a92b9E8429a1', diff --git a/src/features/multiProvider.ts b/src/features/multiProvider.ts index 893db8d6..7d5e7886 100644 --- a/src/features/multiProvider.ts +++ b/src/features/multiProvider.ts @@ -44,6 +44,30 @@ class MultiProtocolProvider extends MultiProvider { if (metadata?.protocol && metadata.protocol !== ProtocolType.Ethereum) return null; return super.tryGetSigner(chainNameOrId); } + + override async tryGetExplorerAddressUrl( + chainNameOrId: ChainName | number, + address?: string, + ): Promise { + const url = await super.tryGetExplorerAddressUrl(chainNameOrId, address); + // TODO hacking fix for solana explorer url here + if (this.getChainName(chainNameOrId) === 'solanadevnet') { + return `${url}?cluster=devnet`; + } + return url; + } + + override tryGetExplorerTxUrl( + chainNameOrId: ChainName | number, + response: { hash: string }, + ): string | null { + const url = super.tryGetExplorerTxUrl(chainNameOrId, response); + // TODO hacking fix for solana explorer url here + if (this.getChainName(chainNameOrId) === 'solanadevnet') { + return `${url}?cluster=devnet`; + } + return url; + } } let multiProvider: MultiProtocolProvider; diff --git a/src/features/transfer/TransferTokenForm.tsx b/src/features/transfer/TransferTokenForm.tsx index 08ea52af..804a54ca 100644 --- a/src/features/transfer/TransferTokenForm.tsx +++ b/src/features/transfer/TransferTokenForm.tsx @@ -1,5 +1,7 @@ +import BigNumber from 'bignumber.js'; import { Form, Formik, useFormikContext } from 'formik'; -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { toast } from 'react-toastify'; import { ProtocolSmallestUnit } from '@hyperlane-xyz/sdk'; import { WideChevron } from '@hyperlane-xyz/widgets'; @@ -208,6 +210,29 @@ function RecipientSection({ const { values } = useFormikContext(); const { balance, decimals } = useDestinationBalance(values, tokenRoutes); + // TODO hacking in a crude way to detect transfer completions by triggering + // toast on recipientAddress balance increase. This is not ideal because it + // could confuse unrelated balance changes for message delivery and it + // doesn't update the store state yet + const recipientAddress = values.recipientAddress; + const prevRecipientBalance = useRef<{ balance?: string; recipientAddress?: string }>({ + balance: '', + recipientAddress: '', + }); + useEffect(() => { + if ( + recipientAddress && + balance && + prevRecipientBalance.current.balance && + prevRecipientBalance.current.recipientAddress === recipientAddress && + new BigNumber(balance).gt(prevRecipientBalance.current.balance) + ) { + toast.success('Recipient has received funds, transfer complete!'); + } else { + prevRecipientBalance.current = { balance, recipientAddress }; + } + }, [balance, recipientAddress, prevRecipientBalance]); + return (
@@ -332,7 +357,10 @@ function SelfButton({ disabled }: { disabled?: boolean }) { const { values, setFieldValue } = useFormikContext(); const address = useAccountForChain(values.destinationCaip2Id)?.address; const onClick = () => { - if (address && !disabled) setFieldValue('recipientAddress', address); + if (disabled) return; + if (address) setFieldValue('recipientAddress', address); + else + toast.warn(`No wallet connected for chain ${getChainDisplayName(values.destinationCaip2Id)}`); }; return ( { try { if (originTxHash) { - const originTx = multiProvider.tryGetExplorerTxUrl(chain, { hash: originTxHash }); + const originTx = multiProvider.tryGetExplorerTxUrl(originChain, { hash: originTxHash }); if (originTx) setOriginTxUrl(originTx); } const [fromUrl, toUrl, tokenUrl] = await Promise.all([ - multiProvider.tryGetExplorerAddressUrl(chain, activeAccountAddress), - multiProvider.tryGetExplorerAddressUrl(chain, recipientAddress), - multiProvider.tryGetExplorerAddressUrl(chain, tokenAddress), + multiProvider.tryGetExplorerAddressUrl(originChain, activeAccountAddress), + multiProvider.tryGetExplorerAddressUrl(destChain, recipientAddress), + multiProvider.tryGetExplorerAddressUrl(originChain, tokenAddress), ]); if (fromUrl) setFromUrl(fromUrl); if (toUrl) setToUrl(toUrl); @@ -63,7 +64,15 @@ export function TransfersDetailsModal({ } catch (error) { logger.error('Error fetching URLs:', error); } - }, [activeAccountAddress, originTxHash, multiProvider, recipientAddress, chain, tokenAddress]); + }, [ + activeAccountAddress, + originTxHash, + multiProvider, + recipientAddress, + originChain, + destChain, + tokenAddress, + ]); useEffect(() => { if (!transfer) return; diff --git a/src/features/wallet/SideBarMenu.tsx b/src/features/wallet/SideBarMenu.tsx index 80025ee8..d2def77e 100644 --- a/src/features/wallet/SideBarMenu.tsx +++ b/src/features/wallet/SideBarMenu.tsx @@ -110,11 +110,11 @@ export function SideBarMenu({ )} -
-
+
+
Connected Wallets
-
+
{readyAccounts.map((a) => (
-
+
Transfer History
-
- {sortedTransfers?.length > 0 && - sortedTransfers.map((t) => ( - - ))} +
+ {STATUSES_WITH_ICON.includes(t.status) ? ( + + ) : ( + + )} +
+ + ))} +
+ {sortedTransfers?.length > 0 && ( + + )}
-
{selectedTransfer && ( diff --git a/src/utils/links.ts b/src/utils/links.ts index bb01485a..c84343b2 100644 --- a/src/utils/links.ts +++ b/src/utils/links.ts @@ -7,6 +7,9 @@ import { toBase64 } from './base64'; // TODO test with solana chain config, or disallow it export function getHypExplorerLink(originCaip2Id: Caip2Id, msgId?: string) { + // TODO Disabling this for eclipse for now + if ('true') return null; + if (!originCaip2Id || !msgId) return null; const baseLink = `${links.explorer}/message/${msgId}`; if (isPermissionlessChain(originCaip2Id)) { From 35c72462015d73dc452c8e3558687f892ebe6b4c Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Mon, 14 Aug 2023 16:58:56 -0400 Subject: [PATCH 11/24] Catch destination balance checking --- src/features/transfer/useTokenTransfer.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 4406bf9b..bddbab34 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -211,18 +211,20 @@ async function ensureSufficientCollateral( if (!targetRoute) return; const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(targetRoute); - const destinationBalance = await adapter.getBalance(targetRoute.baseRouterAddress); + try { + const destinationBalance = await adapter.getBalance(targetRoute.baseRouterAddress); - const destinationBalanceInOriginDecimals = convertDecimals( - route.destDecimals, - route.originDecimals, - destinationBalance, - ); + const destinationBalanceInOriginDecimals = convertDecimals( + route.destDecimals, + route.originDecimals, + destinationBalance, + ); - if (destinationBalanceInOriginDecimals.lt(weiAmount)) { - toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); - throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); - } + if (destinationBalanceInOriginDecimals.lt(weiAmount)) { + toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); + throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); + } + } catch (error) {} } interface ExecuteTransferParams { From 3f9b6a5958057b89d9bbd1413ae5722d6c51f5bd Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Mon, 14 Aug 2023 17:02:44 -0400 Subject: [PATCH 12/24] Fix lint --- src/features/transfer/useTokenTransfer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index bddbab34..b8768b1b 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -224,6 +224,7 @@ async function ensureSufficientCollateral( toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); } + // eslint-disable-next-line no-empty } catch (error) {} } From 8cf18087ed07df9c1c745702130e44b9d4ad236c Mon Sep 17 00:00:00 2001 From: Nam Chu Hoai Date: Mon, 14 Aug 2023 17:21:06 -0400 Subject: [PATCH 13/24] Brand Nautilus --- src/components/nav/Footer.tsx | 4 ++-- src/components/nav/Header.tsx | 4 ---- src/images/logos/app-logo.svg | 32 +++++++++++++++++++++++++++++++- src/images/logos/app-title.svg | 27 ++++++++++++++++++++++++++- src/pages/_document.tsx | 6 +++--- 5 files changed, 62 insertions(+), 11 deletions(-) diff --git a/src/components/nav/Footer.tsx b/src/components/nav/Footer.tsx index 7af62209..41fb214b 100644 --- a/src/components/nav/Footer.tsx +++ b/src/components/nav/Footer.tsx @@ -18,9 +18,9 @@ export function Footer() {

- Go interchain + Bridge ZBC with the Nautilus Chain Bridge
- with Hyperlane + Build with Hyperlane

diff --git a/src/components/nav/Header.tsx b/src/components/nav/Header.tsx index 96eae461..e22f343e 100644 --- a/src/components/nav/Header.tsx +++ b/src/components/nav/Header.tsx @@ -2,8 +2,6 @@ import Image from 'next/image'; import Link from 'next/link'; import { WalletControlBar } from '../../features/wallet/WalletControlBar'; -import Logo from '../../images/logos/app-logo.svg'; -import Name from '../../images/logos/app-name.svg'; import Title from '../../images/logos/app-title.svg'; export function Header() { @@ -11,8 +9,6 @@ export function Header() {
- -
diff --git a/src/images/logos/app-logo.svg b/src/images/logos/app-logo.svg index 39b16cad..6f04b152 100644 --- a/src/images/logos/app-logo.svg +++ b/src/images/logos/app-logo.svg @@ -1 +1,31 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/images/logos/app-title.svg b/src/images/logos/app-title.svg index 073c8aae..5721172e 100644 --- a/src/images/logos/app-title.svg +++ b/src/images/logos/app-title.svg @@ -1 +1,26 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index fe391a3f..f8144e8c 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -15,7 +15,7 @@ export default function Document() { - + - + - + Date: Mon, 14 Aug 2023 19:21:33 -0400 Subject: [PATCH 14/24] Support deposit-only mode --- src/components/tip/TipCard.tsx | 6 +++--- src/consts/config.ts | 5 ++++- src/features/transfer/TransferTokenForm.tsx | 7 +++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/tip/TipCard.tsx b/src/components/tip/TipCard.tsx index 0b1c69c0..d4d779ff 100644 --- a/src/components/tip/TipCard.tsx +++ b/src/components/tip/TipCard.tsx @@ -12,11 +12,11 @@ export function TipCard() { if (!show) return null; return (
-

Bridge Tokens with Hyperlane Warp Routes!

+

⚠️ Nautilus Bridge is in deposit-only mode.

- Warp Routes make it easy to permissionlessly take your tokens interchain. Fork this - template to get started! + Currently, you can bridge from BSC and Solana to Nautilus. Transfers originating Nautilus + are expected to go live September 1st.

Date: Mon, 14 Aug 2023 21:57:10 -0400 Subject: [PATCH 15/24] Reverse direction --- src/features/transfer/TransferTokenForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/transfer/TransferTokenForm.tsx b/src/features/transfer/TransferTokenForm.tsx index 804a54ca..73c6b587 100644 --- a/src/features/transfer/TransferTokenForm.tsx +++ b/src/features/transfer/TransferTokenForm.tsx @@ -467,8 +467,8 @@ function useFormInitialValues(caip2Ids: Caip2Id[], tokenRoutes: RoutesMap): Tran (routes) => routes.length, )[0][0]; return { - originCaip2Id: firstRoute.originCaip2Id, - destinationCaip2Id: firstRoute.destCaip2Id, + originCaip2Id: firstRoute.destCaip2Id, + destinationCaip2Id: firstRoute.originCaip2Id, amount: '', tokenCaip19Id: '' as Caip19Id, recipientAddress: '', From af7ddaf276b772869bceb36c7ac1db23ea19395f Mon Sep 17 00:00:00 2001 From: Trevor Porter Date: Tue, 15 Aug 2023 09:30:34 +0100 Subject: [PATCH 16/24] Fix collateral checking to Solana; fix nautilus tx explorer url --- src/features/multiProvider.ts | 8 ++++++- .../tokens/adapters/SealevelTokenAdapter.ts | 14 +++++++++++ src/features/transfer/useTokenTransfer.ts | 24 +++++++++---------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/features/multiProvider.ts b/src/features/multiProvider.ts index 7d5e7886..5e3a6d05 100644 --- a/src/features/multiProvider.ts +++ b/src/features/multiProvider.ts @@ -62,9 +62,15 @@ class MultiProtocolProvider extends MultiProvider { response: { hash: string }, ): string | null { const url = super.tryGetExplorerTxUrl(chainNameOrId, response); + if (!url) return null; + + const chainName = this.getChainName(chainNameOrId); // TODO hacking fix for solana explorer url here - if (this.getChainName(chainNameOrId) === 'solanadevnet') { + if (chainName === 'solanadevnet') { return `${url}?cluster=devnet`; + } else if (chainName === 'nautilus' || chainName === 'proteustestnet') { + // TODO hacking fix for nautilus explorer url here + return url.replaceAll('/tx/', '/transaction/'); } return url; } diff --git a/src/features/tokens/adapters/SealevelTokenAdapter.ts b/src/features/tokens/adapters/SealevelTokenAdapter.ts index ba5cb79d..3adabb61 100644 --- a/src/features/tokens/adapters/SealevelTokenAdapter.ts +++ b/src/features/tokens/adapters/SealevelTokenAdapter.ts @@ -364,6 +364,20 @@ export class SealevelHypNativeAdapter extends SealevelHypTokenAdapter { // Interacts with Hyp Collateral token programs export class SealevelHypCollateralAdapter extends SealevelHypTokenAdapter { + async getBalance(owner: Address): Promise { + // Special case where the owner is the warp route program ID. + // This is because collateral warp routes don't hold escrowed collateral + // tokens in their associated token account - instead, they hold them in + // the escrow account. + if (owner === this.warpRouteProgramId) { + const collateralAccount = this.deriveEscrowAccount(); + const response = await this.connection.getTokenAccountBalance(collateralAccount); + return response.value.amount; + } + + return super.getBalance(owner); + } + override getTransferInstructionKeyList( sender: PublicKey, mailbox: PublicKey, diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index b8768b1b..5fa4f39e 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -211,21 +211,19 @@ async function ensureSufficientCollateral( if (!targetRoute) return; const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(targetRoute); - try { - const destinationBalance = await adapter.getBalance(targetRoute.baseRouterAddress); - const destinationBalanceInOriginDecimals = convertDecimals( - route.destDecimals, - route.originDecimals, - destinationBalance, - ); + const destinationBalance = await adapter.getBalance(targetRoute.baseRouterAddress); - if (destinationBalanceInOriginDecimals.lt(weiAmount)) { - toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); - throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); - } - // eslint-disable-next-line no-empty - } catch (error) {} + const destinationBalanceInOriginDecimals = convertDecimals( + route.destDecimals, + route.originDecimals, + destinationBalance, + ); + + if (destinationBalanceInOriginDecimals.lt(weiAmount)) { + toast.error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); + throw new Error(COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR); + } } interface ExecuteTransferParams { From 30f7a02c545b76fae8c05eb7e50a12ec01998e28 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 15 Aug 2023 10:42:13 -0400 Subject: [PATCH 17/24] Set Rainbowkit initialChain to Proteus --- src/features/wallet/EvmWalletContext.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/wallet/EvmWalletContext.tsx b/src/features/wallet/EvmWalletContext.tsx index e8fe5241..18f618aa 100644 --- a/src/features/wallet/EvmWalletContext.tsx +++ b/src/features/wallet/EvmWalletContext.tsx @@ -65,6 +65,7 @@ export function EvmWalletContext({ children }: PropsWithChildren) { borderRadius: 'small', fontStack: 'system', })} + initialChain={88002} > {children} From 49cda5bd90aaddc763556bf50267a5e141cd8fb0 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Mon, 21 Aug 2023 16:53:23 -0400 Subject: [PATCH 18/24] Fix build error in balances --- src/features/tokens/balances.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/features/tokens/balances.tsx b/src/features/tokens/balances.tsx index 5066d2fa..d756160d 100644 --- a/src/features/tokens/balances.tsx +++ b/src/features/tokens/balances.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import { areAddressesEqual, isValidAddress } from '../../utils/addresses'; import { logger } from '../../utils/logger'; import { getProtocolType } from '../caip/chains'; -import { parseCaip19Id, tryGetChainIdFromToken } from '../caip/tokens'; +import { getChainIdFromToken, parseCaip19Id, tryGetChainIdFromToken } from '../caip/tokens'; import { getProvider } from '../multiProvider'; import { useStore } from '../store'; import { TransferFormValues } from '../transfer/types'; @@ -77,9 +77,9 @@ export function useDestinationBalance( // This searches for the route where the origin chain is destinationCaip2Id // and the destination chain is originCaip2Id and where the origin is a base token. - const targetBaseCaip19Id = tokenRoutes[destinationCaip2Id][originCaip2Id].find((r) => - r.baseCaip19Id.startsWith(destinationCaip2Id), - )!.baseCaip19Id; + const targetBaseCaip19Id = tokenRoutes[destinationCaip2Id][originCaip2Id].find( + (r) => getChainIdFromToken(r.baseTokenCaip19Id) === destinationCaip2Id, + )!.baseTokenCaip19Id; const route = getTokenRoute( destinationCaip2Id, originCaip2Id, From 101496635145cdd3e67f403dd3f0ad742f48a5c8 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 22 Aug 2023 13:19:26 -0400 Subject: [PATCH 19/24] Setup Jest unit testing Create new CollateralToCollateral route type Refactor route computation into separate file Add unit tests for route computation --- .eslintignore | 3 +- .github/workflows/ci.yml | 29 +- jest.config.js | 16 + package.json | 3 + src/features/tokens/SelectOrInputTokenIds.tsx | 2 +- .../tokens/adapters/AdapterFactory.ts | 16 +- src/features/tokens/routes/fetch.test.ts | 252 ++ src/features/tokens/routes/fetch.ts | 166 ++ src/features/tokens/routes/hooks.ts | 150 +- src/features/tokens/routes/types.ts | 5 +- src/features/tokens/routes/utils.ts | 28 +- src/features/transfer/useTokenTransfer.ts | 12 +- yarn.lock | 2208 ++++++++++++++++- 13 files changed, 2682 insertions(+), 208 deletions(-) create mode 100644 jest.config.js create mode 100644 src/features/tokens/routes/fetch.test.ts create mode 100644 src/features/tokens/routes/fetch.ts diff --git a/.eslintignore b/.eslintignore index a72b3c43..cc71deb1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,5 @@ build coverage postcss.config.js next.config.js -tailwind.config.js \ No newline at end of file +tailwind.config.js +jest.config.js \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 279433bb..be539c14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,6 @@ jobs: with: path: .//node_modules key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: yarn-install # Check out the lockfile from main, reinstall, and then # verify the lockfile matches what was committed. @@ -37,19 +36,16 @@ jobs: needs: [install] steps: - uses: actions/checkout@v2 - - name: yarn-cache uses: actions/cache@v2 with: path: .//node_modules key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: build-cache uses: actions/cache@v2 with: path: ./* key: ${{ github.sha }} - - name: build run: yarn run build env: @@ -64,7 +60,6 @@ jobs: with: path: .//node_modules key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: prettier run: | yarn run prettier @@ -83,19 +78,17 @@ jobs: with: path: .//node_modules key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} - - name: lint run: yarn run lint - # test: - # runs-on: ubuntu-latest - # needs: [build] - # steps: - # - uses: actions/checkout@v2 - # - uses: actions/cache@v2 - # with: - # path: ./* - # key: ${{ github.sha }} - - # - name: test - # run: yarn run test + test: + runs-on: ubuntu-latest + needs: [build] + steps: + - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: .//node_modules + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + - name: test + run: yarn run test diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..b1d5d03c --- /dev/null +++ b/jest.config.js @@ -0,0 +1,16 @@ +const nextJest = require('next/jest') + +const createJestConfig = nextJest({ + // Provide the path to your Next.js app to load next.config.js and .env files in your test environment + dir: './', +}) + +// Add any custom config to be passed to Jest +/** @type {import('jest').Config} */ +const customJestConfig = { + // Add more setup options before each test is run + // setupFilesAfterEnv: ['/jest.setup.js'], +} + +// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async +module.exports = createJestConfig(customJestConfig) \ No newline at end of file diff --git a/package.json b/package.json index 24dca102..fc4c1152 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.1.1", + "@types/jest": "^29.5.3", "@types/node": "^18.11.18", "@types/react": "^18.2.7", "@types/react-dom": "^18.2.4", @@ -40,6 +41,7 @@ "eslint": "^8.41.0", "eslint-config-next": "^13.4.3", "eslint-config-prettier": "^8.8.0", + "jest": "^29.6.3", "postcss": "^8.4.23", "prettier": "^2.8.8", "tailwindcss": "^3.3.2", @@ -62,6 +64,7 @@ "typecheck": "tsc", "lint": "next lint", "start": "next start", + "test": "jest", "prettier": "prettier --write ./src" }, "types": "dist/src/index.d.ts", diff --git a/src/features/tokens/SelectOrInputTokenIds.tsx b/src/features/tokens/SelectOrInputTokenIds.tsx index 149e85b2..37661bb8 100644 --- a/src/features/tokens/SelectOrInputTokenIds.tsx +++ b/src/features/tokens/SelectOrInputTokenIds.tsx @@ -24,7 +24,7 @@ export function SelectOrInputTokenIds({ const route = getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); let activeToken = '' as TokenCaip19Id; - if (route?.type === RouteType.BaseToSynthetic) { + if (route?.type === RouteType.CollateralToSynthetic) { // If the origin is the base chain, use the collateralized token for balance checking activeToken = tokenCaip19Id; } else if (route) { diff --git a/src/features/tokens/adapters/AdapterFactory.ts b/src/features/tokens/adapters/AdapterFactory.ts index 3d6d20bc..7de66b60 100644 --- a/src/features/tokens/adapters/AdapterFactory.ts +++ b/src/features/tokens/adapters/AdapterFactory.ts @@ -12,7 +12,13 @@ import { parseCaip19Id, } from '../../caip/tokens'; import { getMultiProvider, getProvider } from '../../multiProvider'; -import { Route, RouteType } from '../routes/types'; +import { Route } from '../routes/types'; +import { + isRouteFromCollateral, + isRouteFromSynthetic, + isRouteToCollateral, + isRouteToSynthetic, +} from '../routes/utils'; import { EvmHypCollateralAdapter, @@ -75,7 +81,7 @@ export class AdapterFactory { static HypTokenAdapterFromRouteOrigin(route: Route) { const { type, originCaip2Id, originRouterAddress, baseTokenCaip19Id } = route; - if (type === RouteType.BaseToSynthetic) { + if (isRouteFromCollateral(route)) { return AdapterFactory.selectHypAdapter( originCaip2Id, originRouterAddress, @@ -83,7 +89,7 @@ export class AdapterFactory { EvmHypCollateralAdapter, isNativeToken(baseTokenCaip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, ); - } else if (type === RouteType.SyntheticToBase || type === RouteType.SyntheticToSynthetic) { + } else if (isRouteFromSynthetic(route)) { return AdapterFactory.selectHypAdapter( originCaip2Id, originRouterAddress, @@ -98,7 +104,7 @@ export class AdapterFactory { static HypTokenAdapterFromRouteDest(route: Route) { const { type, destCaip2Id, destRouterAddress, baseTokenCaip19Id } = route; - if (type === RouteType.SyntheticToBase) { + if (isRouteToCollateral(route)) { return AdapterFactory.selectHypAdapter( destCaip2Id, destRouterAddress, @@ -106,7 +112,7 @@ export class AdapterFactory { EvmHypCollateralAdapter, isNativeToken(baseTokenCaip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, ); - } else if (type === RouteType.BaseToSynthetic || type === RouteType.SyntheticToSynthetic) { + } else if (isRouteToSynthetic(route)) { return AdapterFactory.selectHypAdapter( destCaip2Id, destRouterAddress, diff --git a/src/features/tokens/routes/fetch.test.ts b/src/features/tokens/routes/fetch.test.ts new file mode 100644 index 00000000..1fabfb59 --- /dev/null +++ b/src/features/tokens/routes/fetch.test.ts @@ -0,0 +1,252 @@ +import { TokenType } from '@hyperlane-xyz/hyperlane-token'; + +import { SOL_ZERO_ADDRESS } from '../../../consts/values'; + +import { computeTokenRoutes } from './fetch'; + +describe('computeTokenRoutes', () => { + it('Handles empty list', () => { + const routesMap = computeTokenRoutes([]); + expect(routesMap).toBeTruthy(); + expect(Object.values(routesMap).length).toBe(0); + }); + + it('Handles basic 3-node route', () => { + const routesMap = computeTokenRoutes([ + { + type: TokenType.collateral, + tokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + routerAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + name: 'Weth', + symbol: 'WETH', + decimals: 18, + hypTokens: [ + { + decimals: 18, + tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + }, + { + decimals: 18, + tokenCaip19Id: 'ethereum:44787/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + }, + ], + }, + ]); + expect(routesMap).toEqual({ + 'ethereum:5': { + 'ethereum:11155111': [ + { + type: 'collateralToSynthetic', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:5', + originRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originDecimals: 18, + destCaip2Id: 'ethereum:11155111', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + 'ethereum:44787': [ + { + type: 'collateralToSynthetic', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:5', + originRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originDecimals: 18, + destCaip2Id: 'ethereum:44787', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + }, + 'ethereum:11155111': { + 'ethereum:5': [ + { + type: 'syntheticToCollateral', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:11155111', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'ethereum:5', + destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + destDecimals: 18, + }, + ], + 'ethereum:44787': [ + { + type: 'syntheticToSynthetic', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:11155111', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'ethereum:44787', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + }, + 'ethereum:44787': { + 'ethereum:5': [ + { + type: 'syntheticToCollateral', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:44787', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'ethereum:5', + destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + destDecimals: 18, + }, + ], + 'ethereum:11155111': [ + { + type: 'syntheticToSynthetic', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:44787', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'ethereum:11155111', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + }, + }); + }); + + it('Handles multi-collateral route', () => { + const routesMap = computeTokenRoutes([ + { + type: TokenType.collateral, + tokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + routerAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + name: 'Weth', + symbol: 'WETH', + decimals: 18, + hypTokens: [ + { + decimals: 18, + tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + }, + { + decimals: 6, + tokenCaip19Id: 'sealevel:1399811151/native:PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + }, + ], + }, + { + type: TokenType.native, + tokenCaip19Id: `sealevel:1399811151/native:${SOL_ZERO_ADDRESS}`, + routerAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + name: 'Zebec', + symbol: 'ZBC', + decimals: 6, + hypTokens: [ + { + decimals: 18, + tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + }, + { + decimals: 18, + tokenCaip19Id: 'ethereum:5/erc20:0x145de8760021c4ac6676376691b78038d3DE9097', + }, + ], + }, + ]); + expect(routesMap).toEqual({ + 'ethereum:5': { + 'ethereum:11155111': [ + { + type: 'collateralToSynthetic', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:5', + originRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originDecimals: 18, + destCaip2Id: 'ethereum:11155111', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + 'sealevel:1399811151': [ + { + type: 'collateralToCollateral', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:5', + originRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originDecimals: 18, + destCaip2Id: 'sealevel:1399811151', + destRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + destDecimals: 6, + }, + ], + }, + 'ethereum:11155111': { + 'ethereum:5': [ + { + type: 'syntheticToCollateral', + baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', + baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + originCaip2Id: 'ethereum:11155111', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'ethereum:5', + destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + destDecimals: 18, + }, + ], + 'sealevel:1399811151': [ + { + type: 'syntheticToCollateral', + baseTokenCaip19Id: + 'sealevel:1399811151/native:00000000000000000000000000000000000000000000', + baseRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + originCaip2Id: 'ethereum:11155111', + originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originDecimals: 18, + destCaip2Id: 'sealevel:1399811151', + destRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + destDecimals: 6, + }, + ], + }, + 'sealevel:1399811151': { + 'ethereum:5': [ + { + type: 'collateralToCollateral', + baseTokenCaip19Id: + 'sealevel:1399811151/native:00000000000000000000000000000000000000000000', + baseRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + originCaip2Id: 'sealevel:1399811151', + originRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + originDecimals: 6, + destCaip2Id: 'ethereum:5', + destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', + destDecimals: 18, + }, + ], + 'ethereum:11155111': [ + { + type: 'collateralToSynthetic', + baseTokenCaip19Id: + 'sealevel:1399811151/native:00000000000000000000000000000000000000000000', + baseRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + originCaip2Id: 'sealevel:1399811151', + originRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + originDecimals: 6, + destCaip2Id: 'ethereum:11155111', + destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destDecimals: 18, + }, + ], + }, + }); + }); +}); diff --git a/src/features/tokens/routes/fetch.ts b/src/features/tokens/routes/fetch.ts new file mode 100644 index 00000000..acb47501 --- /dev/null +++ b/src/features/tokens/routes/fetch.ts @@ -0,0 +1,166 @@ +import { ProtocolType } from '@hyperlane-xyz/sdk'; + +import { areAddressesEqual, bytesToProtocolAddress } from '../../../utils/addresses'; +import { logger } from '../../../utils/logger'; +import { getCaip2Id } from '../../caip/chains'; +import { + getCaip19Id, + getChainIdFromToken, + isNonFungibleToken, + parseCaip19Id, + resolveAssetNamespace, +} from '../../caip/tokens'; +import { getMultiProvider } from '../../multiProvider'; +import { AdapterFactory } from '../adapters/AdapterFactory'; +import { TokenMetadata, TokenMetadataWithHypTokens } from '../types'; + +import { RouteType, RoutesMap } from './types'; + +export async function fetchRemoteHypTokens( + baseToken: TokenMetadata, + allTokens: TokenMetadata[], +): Promise { + const { + symbol: baseSymbol, + tokenCaip19Id: baseTokenCaip19Id, + routerAddress: baseRouter, + } = baseToken; + const isNft = isNonFungibleToken(baseTokenCaip19Id); + logger.info(`Fetching remote tokens for symbol ${baseSymbol} (${baseTokenCaip19Id})`); + + const baseAdapter = AdapterFactory.HypCollateralAdapterFromAddress(baseTokenCaip19Id, baseRouter); + + const remoteRouters = await baseAdapter.getAllRouters(); + logger.info(`Router addresses found:`, remoteRouters); + + const multiProvider = getMultiProvider(); + const hypTokens = await Promise.all( + remoteRouters.map(async (router) => { + const destMetadata = multiProvider.getChainMetadata(router.domain); + const protocol = destMetadata.protocol || ProtocolType.Ethereum; + const chainCaip2Id = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); + const namespace = resolveAssetNamespace(protocol, false, isNft, true); + const formattedAddress = bytesToProtocolAddress(router.address, protocol); + const tokenCaip19Id = getCaip19Id(chainCaip2Id, namespace, formattedAddress); + if (isNft) return { tokenCaip19Id, decimals: 0 }; + // Attempt to find the decimals from the token list + const routerMetadata = allTokens.find((token) => + areAddressesEqual(formattedAddress, token.routerAddress), + ); + if (routerMetadata) return { tokenCaip19Id, decimals: routerMetadata.decimals }; + // Otherwise try to query the contract + const remoteAdapter = AdapterFactory.HypSyntheticTokenAdapterFromAddress( + baseTokenCaip19Id, + chainCaip2Id, + formattedAddress, + ); + const metadata = await remoteAdapter.getMetadata(); + return { tokenCaip19Id, decimals: metadata.decimals }; + }), + ); + return { ...baseToken, hypTokens }; +} + +// Process token list to populates routesCache with all possible token routes (e.g. router pairs) +export function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { + const tokenRoutes: RoutesMap = {}; + + // Instantiate map structure + const allChainIds = getChainsFromTokens(tokens); + for (const origin of allChainIds) { + tokenRoutes[origin] = {}; + for (const dest of allChainIds) { + if (origin === dest) continue; + tokenRoutes[origin][dest] = []; + } + } + + // Compute all possible routes, in both directions + for (const token of tokens) { + for (const remoteHypToken of token.hypTokens) { + const { + tokenCaip19Id: baseTokenCaip19Id, + routerAddress: baseRouterAddress, + decimals: baseDecimals, + } = token; + const baseChainCaip2Id = getChainIdFromToken(baseTokenCaip19Id); + const { chainCaip2Id: remoteCaip2Id, address: remoteRouterAddress } = parseCaip19Id( + remoteHypToken.tokenCaip19Id, + ); + const remoteDecimals = remoteHypToken.decimals; + // Check if the token list contains the dest router address, meaning it's also a base collateral token + const isRemoteCollateral = tokensHasRouter(tokens, remoteRouterAddress); + const commonRouteProps = { baseTokenCaip19Id, baseRouterAddress }; + + // Register a route from the base to the remote + tokenRoutes[baseChainCaip2Id][remoteCaip2Id]?.push({ + type: isRemoteCollateral + ? RouteType.CollateralToCollateral + : RouteType.CollateralToSynthetic, + ...commonRouteProps, + originCaip2Id: baseChainCaip2Id, + originRouterAddress: baseRouterAddress, + originDecimals: baseDecimals, + destCaip2Id: remoteCaip2Id, + destRouterAddress: remoteRouterAddress, + destDecimals: remoteDecimals, + }); + + // If the remote is not a synthetic (i.e. it's a native/collateral token with it's own config) + // then stop here to avoid duplicate route entries. + if (isRemoteCollateral) continue; + + // Register a route back from the synthetic remote to the base + tokenRoutes[remoteCaip2Id][baseChainCaip2Id]?.push({ + type: RouteType.SyntheticToCollateral, + ...commonRouteProps, + originCaip2Id: remoteCaip2Id, + originRouterAddress: remoteRouterAddress, + originDecimals: remoteDecimals, + destCaip2Id: baseChainCaip2Id, + destRouterAddress: baseRouterAddress, + destDecimals: baseDecimals, + }); + + // Now create routes from the remote synthetic token to all other hypTokens + // This assumes the synthetics were all enrolled to connect to each other + // which is the deployer's default behavior + for (const otherHypToken of token.hypTokens) { + const { chainCaip2Id: otherSynCaip2Id, address: otherHypTokenAddress } = parseCaip19Id( + otherHypToken.tokenCaip19Id, + ); + // Skip if it's same hypToken as parent loop (no route to self) + // or if if remote isn't a synthetic + if (otherHypToken === remoteHypToken || tokensHasRouter(tokens, otherHypTokenAddress)) + continue; + + tokenRoutes[remoteCaip2Id][otherSynCaip2Id]?.push({ + type: RouteType.SyntheticToSynthetic, + ...commonRouteProps, + originCaip2Id: remoteCaip2Id, + originRouterAddress: remoteRouterAddress, + originDecimals: remoteDecimals, + destCaip2Id: otherSynCaip2Id, + destRouterAddress: otherHypTokenAddress, + destDecimals: otherHypToken.decimals, + }); + } + } + } + return tokenRoutes; +} + +function getChainsFromTokens(tokens: TokenMetadataWithHypTokens[]): ChainCaip2Id[] { + const chains = new Set(); + for (const token of tokens) { + chains.add(getChainIdFromToken(token.tokenCaip19Id)); + for (const hypToken of token.hypTokens) { + chains.add(getChainIdFromToken(hypToken.tokenCaip19Id)); + } + } + return Array.from(chains); +} + +function tokensHasRouter(tokens: TokenMetadataWithHypTokens[], router: Address) { + return !!tokens.find((t) => areAddressesEqual(t.routerAddress, router)); +} diff --git a/src/features/tokens/routes/hooks.ts b/src/features/tokens/routes/hooks.ts index e309bb0d..b393be97 100644 --- a/src/features/tokens/routes/hooks.ts +++ b/src/features/tokens/routes/hooks.ts @@ -1,24 +1,13 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; -import { ProtocolType } from '@hyperlane-xyz/sdk'; - -import { areAddressesEqual, bytesToProtocolAddress } from '../../../utils/addresses'; import { logger } from '../../../utils/logger'; -import { getCaip2Id } from '../../caip/chains'; -import { - getCaip19Id, - getChainIdFromToken, - isNonFungibleToken, - parseCaip19Id, - resolveAssetNamespace, -} from '../../caip/tokens'; -import { getMultiProvider } from '../../multiProvider'; -import { AdapterFactory } from '../adapters/AdapterFactory'; +import { getChainIdFromToken } from '../../caip/tokens'; import { getTokens, parseTokens } from '../metadata'; -import { TokenMetadata, TokenMetadataWithHypTokens } from '../types'; +import { TokenMetadataWithHypTokens } from '../types'; -import { RouteType, RoutesMap } from './types'; +import { computeTokenRoutes, fetchRemoteHypTokens } from './fetch'; +import { RoutesMap } from './types'; export function useTokenRoutes() { const { @@ -44,137 +33,6 @@ export function useTokenRoutes() { return { isLoading, error, tokenRoutes }; } -async function fetchRemoteHypTokens( - baseToken: TokenMetadata, - allTokens: TokenMetadata[], -): Promise { - const { - symbol: baseSymbol, - tokenCaip19Id: baseTokenCaip19Id, - routerAddress: baseRouter, - } = baseToken; - const isNft = isNonFungibleToken(baseTokenCaip19Id); - logger.info(`Fetching remote tokens for symbol ${baseSymbol} (${baseTokenCaip19Id})`); - - const baseAdapter = AdapterFactory.HypCollateralAdapterFromAddress(baseTokenCaip19Id, baseRouter); - - const remoteRouters = await baseAdapter.getAllRouters(); - logger.info(`Router addresses found:`, remoteRouters); - - const multiProvider = getMultiProvider(); - const hypTokens = await Promise.all( - remoteRouters.map(async (router) => { - const destMetadata = multiProvider.getChainMetadata(router.domain); - const protocol = destMetadata.protocol || ProtocolType.Ethereum; - const chainCaip2Id = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); - const namespace = resolveAssetNamespace(protocol, false, isNft, true); - const formattedAddress = bytesToProtocolAddress(router.address, protocol); - const tokenCaip19Id = getCaip19Id(chainCaip2Id, namespace, formattedAddress); - if (isNft) return { tokenCaip19Id, decimals: 0 }; - // Attempt to find the decimals from the token list - const routerMetadata = allTokens.find((token) => - areAddressesEqual(formattedAddress, token.routerAddress), - ); - if (routerMetadata) return { tokenCaip19Id, decimals: routerMetadata.decimals }; - // Otherwise try to query the contract - const remoteAdapter = AdapterFactory.HypSyntheticTokenAdapterFromAddress( - baseTokenCaip19Id, - chainCaip2Id, - formattedAddress, - ); - const metadata = await remoteAdapter.getMetadata(); - return { tokenCaip19Id, decimals: metadata.decimals }; - }), - ); - return { ...baseToken, hypTokens }; -} - -// Process token list to populates routesCache with all possible token routes (e.g. router pairs) -function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { - const tokenRoutes: RoutesMap = {}; - - // Instantiate map structure - const allChainIds = getChainsFromTokens(tokens); - for (const origin of allChainIds) { - tokenRoutes[origin] = {}; - for (const dest of allChainIds) { - if (origin === dest) continue; - tokenRoutes[origin][dest] = []; - } - } - - // Compute all possible routes, in both directions - for (const token of tokens) { - for (const hypToken of token.hypTokens) { - const { - tokenCaip19Id: baseTokenCaip19Id, - routerAddress: baseRouterAddress, - decimals: baseDecimals, - } = token; - const baseChainCaip2Id = getChainIdFromToken(baseTokenCaip19Id); - const { chainCaip2Id: syntheticCaip2Id, address: syntheticRouterAddress } = parseCaip19Id( - hypToken.tokenCaip19Id, - ); - const syntheticDecimals = hypToken.decimals; - - const commonRouteProps = { - baseTokenCaip19Id, - baseRouterAddress, - }; - tokenRoutes[baseChainCaip2Id][syntheticCaip2Id]?.push({ - type: RouteType.BaseToSynthetic, - ...commonRouteProps, - originCaip2Id: baseChainCaip2Id, - originRouterAddress: baseRouterAddress, - originDecimals: baseDecimals, - destCaip2Id: syntheticCaip2Id, - destRouterAddress: syntheticRouterAddress, - destDecimals: syntheticDecimals, - }); - tokenRoutes[syntheticCaip2Id][baseChainCaip2Id]?.push({ - type: RouteType.SyntheticToBase, - ...commonRouteProps, - originCaip2Id: syntheticCaip2Id, - originRouterAddress: syntheticRouterAddress, - originDecimals: syntheticDecimals, - destCaip2Id: baseChainCaip2Id, - destRouterAddress: baseRouterAddress, - destDecimals: baseDecimals, - }); - - for (const otherHypToken of token.hypTokens) { - // Skip if it's same hypToken as parent loop (no route to self) - if (otherHypToken === hypToken) continue; - const { chainCaip2Id: otherSynCaip2Id, address: otherHypTokenAddress } = parseCaip19Id( - otherHypToken.tokenCaip19Id, - ); - tokenRoutes[syntheticCaip2Id][otherSynCaip2Id]?.push({ - type: RouteType.SyntheticToSynthetic, - ...commonRouteProps, - originCaip2Id: syntheticCaip2Id, - originRouterAddress: syntheticRouterAddress, - originDecimals: syntheticDecimals, - destCaip2Id: otherSynCaip2Id, - destRouterAddress: otherHypTokenAddress, - destDecimals: otherHypToken.decimals, - }); - } - } - } - return tokenRoutes; -} - -function getChainsFromTokens(tokens: TokenMetadataWithHypTokens[]): ChainCaip2Id[] { - const chains = new Set(); - for (const token of tokens) { - chains.add(getChainIdFromToken(token.tokenCaip19Id)); - for (const hypToken of token.hypTokens) { - chains.add(getChainIdFromToken(hypToken.tokenCaip19Id)); - } - } - return Array.from(chains); -} - export function useRouteChains(tokenRoutes: RoutesMap): ChainCaip2Id[] { return useMemo(() => { const allCaip2Ids = Object.keys(tokenRoutes) as ChainCaip2Id[]; diff --git a/src/features/tokens/routes/types.ts b/src/features/tokens/routes/types.ts index 07590623..31772af0 100644 --- a/src/features/tokens/routes/types.ts +++ b/src/features/tokens/routes/types.ts @@ -1,7 +1,8 @@ export enum RouteType { - BaseToSynthetic = 'baseToSynthetic', + CollateralToCollateral = 'collateralToCollateral', + CollateralToSynthetic = 'collateralToSynthetic', SyntheticToSynthetic = 'syntheticToSynthetic', - SyntheticToBase = 'syntheticToBase', + SyntheticToCollateral = 'syntheticToCollateral', } export interface Route { diff --git a/src/features/tokens/routes/utils.ts b/src/features/tokens/routes/utils.ts index d60b3859..81e6774f 100644 --- a/src/features/tokens/routes/utils.ts +++ b/src/features/tokens/routes/utils.ts @@ -1,4 +1,4 @@ -import { Route, RoutesMap } from './types'; +import { Route, RouteType, RoutesMap } from './types'; export function getTokenRoutes( originCaip2Id: ChainCaip2Id, @@ -28,3 +28,29 @@ export function hasTokenRoute( ): boolean { return !!getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); } + +export function isRouteToCollateral(route: Route) { + return ( + route.type === RouteType.CollateralToCollateral || + route.type === RouteType.SyntheticToCollateral + ); +} + +export function isRouteFromCollateral(route: Route) { + return ( + route.type === RouteType.CollateralToCollateral || + route.type === RouteType.CollateralToSynthetic + ); +} + +export function isRouteToSynthetic(route: Route) { + return ( + route.type === RouteType.CollateralToSynthetic || route.type === RouteType.SyntheticToSynthetic + ); +} + +export function isRouteFromSynthetic(route: Route) { + return ( + route.type === RouteType.SyntheticToCollateral || route.type === RouteType.SyntheticToSynthetic + ); +} diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 7d362975..1c663b7e 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -14,8 +14,8 @@ import { getMultiProvider } from '../multiProvider'; import { AppState, useStore } from '../store'; import { AdapterFactory } from '../tokens/adapters/AdapterFactory'; import { IHypTokenAdapter } from '../tokens/adapters/ITokenAdapter'; -import { Route, RouteType, RoutesMap } from '../tokens/routes/types'; -import { getTokenRoute } from '../tokens/routes/utils'; +import { Route, RoutesMap } from '../tokens/routes/types'; +import { getTokenRoute, isRouteFromCollateral, isRouteToCollateral } from '../tokens/routes/utils'; import { AccountInfo, ActiveChainInfo, @@ -183,7 +183,7 @@ async function executeTransfer({ // it's possible that the collateral contract balance is insufficient to // cover the remote transfer. This ensures the balance is sufficient or throws. async function ensureSufficientCollateral(route: Route, weiAmount: string, isNft?: boolean) { - if (route.type !== RouteType.SyntheticToBase || isNft) return; + if (!isRouteToCollateral(route) || isNft) return; const adapter = AdapterFactory.TokenAdapterFromAddress(route.baseTokenCaip19Id); logger.debug('Checking collateral balance for token', route.baseTokenCaip19Id); const balance = await adapter.getBalance(route.baseRouterAddress); @@ -215,7 +215,7 @@ async function executeEvmTransfer({ updateStatus, sendTransaction, }: ExecuteTransferParams) { - const { type: routeType, baseRouterAddress, originCaip2Id, baseTokenCaip19Id } = tokenRoute; + const { baseRouterAddress, originCaip2Id, baseTokenCaip19Id } = tokenRoute; if (isTransferApproveRequired(tokenRoute, baseTokenCaip19Id)) { updateStatus(TransferStatus.CreatingApprove); @@ -244,7 +244,7 @@ async function executeEvmTransfer({ logger.debug('Quoted gas payment', gasPayment); // If sending native tokens (e.g. Eth), the gasPayment must be added to the tx value and sent together const txValue = - routeType === RouteType.BaseToSynthetic && isNativeToken(baseTokenCaip19Id) + isRouteFromCollateral(tokenRoute) && isNativeToken(baseTokenCaip19Id) ? BigNumber.from(gasPayment).add(weiAmountOrId) : gasPayment; const transferTxRequest = (await hypTokenAdapter.populateTransferRemoteTx({ @@ -313,7 +313,7 @@ async function executeSealevelTransfer({ export function isTransferApproveRequired(route: Route, tokenCaip19Id: TokenCaip19Id) { return ( !isNativeToken(tokenCaip19Id) && - route.type === RouteType.BaseToSynthetic && + isRouteFromCollateral(route) && getProtocolType(route.originCaip2Id) === ProtocolType.Ethereum ); } diff --git a/yarn.lock b/yarn.lock index 24c92df0..f1112877 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,16 @@ __metadata: languageName: node linkType: hard +"@ampproject/remapping@npm:^2.2.0": + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 + languageName: node + linkType: hard + "@apocentre/alias-sampling@npm:^0.5.3": version: 0.5.3 resolution: "@apocentre/alias-sampling@npm:0.5.3" @@ -19,6 +29,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.10, @babel/code-frame@npm:^7.22.5": + version: 7.22.10 + resolution: "@babel/code-frame@npm:7.22.10" + dependencies: + "@babel/highlight": ^7.22.10 + chalk: ^2.4.2 + checksum: 89a06534ad19759da6203a71bad120b1d7b2ddc016c8e07d4c56b35dea25e7396c6da60a754e8532a86733092b131ae7f661dbe6ba5d165ea777555daa2ed3c9 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -28,6 +48,36 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/compat-data@npm:7.22.9" + checksum: bed77d9044ce948b4327b30dd0de0779fa9f3a7ed1f2d31638714ed00229fa71fc4d1617ae0eb1fad419338d3658d0e9a5a083297451e09e73e078d0347ff808 + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": + version: 7.22.10 + resolution: "@babel/core@npm:7.22.10" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.22.10 + "@babel/generator": ^7.22.10 + "@babel/helper-compilation-targets": ^7.22.10 + "@babel/helper-module-transforms": ^7.22.9 + "@babel/helpers": ^7.22.10 + "@babel/parser": ^7.22.10 + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.10 + "@babel/types": ^7.22.10 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.1 + checksum: cc4efa09209fe1f733cf512e9e4bb50870b191ab2dee8014e34cd6e731f204e48476cc53b4bbd0825d4d342304d577ae43ff5fd8ab3896080673c343321acb32 + languageName: node + linkType: hard + "@babel/generator@npm:7.17.7": version: 7.17.7 resolution: "@babel/generator@npm:7.17.7" @@ -50,6 +100,31 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.22.10, @babel/generator@npm:^7.7.2": + version: 7.22.10 + resolution: "@babel/generator@npm:7.22.10" + dependencies: + "@babel/types": ^7.22.10 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 59a79730abdff9070692834bd3af179e7a9413fa2ff7f83dff3eb888765aeaeb2bfc7b0238a49613ed56e1af05956eff303cc139f2407eda8df974813e486074 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/helper-compilation-targets@npm:7.22.10" + dependencies: + "@babel/compat-data": ^7.22.9 + "@babel/helper-validator-option": ^7.22.5 + browserslist: ^4.21.9 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: f6f1896816392bcff671bbe6e277307729aee53befb4a66ea126e2a91eda78d819a70d06fa384c74ef46c1595544b94dca50bef6c78438d9ffd31776dafbd435 + languageName: node + linkType: hard + "@babel/helper-environment-visitor@npm:^7.16.7": version: 7.18.9 resolution: "@babel/helper-environment-visitor@npm:7.18.9" @@ -57,6 +132,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-environment-visitor@npm:7.22.5" + checksum: 248532077d732a34cd0844eb7b078ff917c3a8ec81a7f133593f71a860a582f05b60f818dc5049c2212e5baa12289c27889a4b81d56ef409b4863db49646c4b1 + languageName: node + linkType: hard + "@babel/helper-function-name@npm:^7.16.7": version: 7.19.0 resolution: "@babel/helper-function-name@npm:7.19.0" @@ -67,6 +149,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-function-name@npm:7.22.5" + dependencies: + "@babel/template": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: 6b1f6ce1b1f4e513bf2c8385a557ea0dd7fa37971b9002ad19268ca4384bbe90c09681fe4c076013f33deabc63a53b341ed91e792de741b4b35e01c00238177a + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.16.7": version: 7.18.6 resolution: "@babel/helper-hoist-variables@npm:7.18.6" @@ -76,6 +168,55 @@ __metadata: languageName: node linkType: hard +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-module-imports@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 9ac2b0404fa38b80bdf2653fbeaf8e8a43ccb41bd505f9741d820ed95d3c4e037c62a1bcdcb6c9527d7798d2e595924c4d025daed73283badc180ada2c9c49ad + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.22.9": + version: 7.22.9 + resolution: "@babel/helper-module-transforms@npm:7.22.9" + dependencies: + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-module-imports": ^7.22.5 + "@babel/helper-simple-access": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/helper-validator-identifier": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 2751f77660518cf4ff027514d6f4794f04598c6393be7b04b8e46c6e21606e11c19f3f57ab6129a9c21bacdf8b3ffe3af87bb401d972f34af2d0ffde02ac3001 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.22.5 + resolution: "@babel/helper-plugin-utils@npm:7.22.5" + checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-simple-access@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: fe9686714caf7d70aedb46c3cce090f8b915b206e09225f1e4dbc416786c2fdbbee40b38b23c268b7ccef749dd2db35f255338fb4f2444429874d900dede5ad2 + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.16.7": version: 7.18.6 resolution: "@babel/helper-split-export-declaration@npm:7.18.6" @@ -85,6 +226,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" + dependencies: + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.19.4": version: 7.19.4 resolution: "@babel/helper-string-parser@npm:7.19.4" @@ -92,6 +242,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.16.7, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -106,6 +263,31 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-identifier@npm:7.22.5" + checksum: 7f0f30113474a28298c12161763b49de5018732290ca4de13cdaefd4fd0d635a6fe3f6686c37a02905fb1e64f21a5ee2b55140cf7b070e729f1bd66866506aea + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-validator-option@npm:7.22.5" + checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/helpers@npm:7.22.10" + dependencies: + "@babel/template": ^7.22.5 + "@babel/traverse": ^7.22.10 + "@babel/types": ^7.22.10 + checksum: 3b1219e362df390b6c5d94b75a53fc1c2eb42927ced0b8022d6a29b833a839696206b9bdad45b4805d05591df49fc16b6fb7db758c9c2ecfe99e3e94cb13020f + languageName: node + linkType: hard + "@babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -117,6 +299,26 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/highlight@npm:7.22.10" + dependencies: + "@babel/helper-validator-identifier": ^7.22.5 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: f714a1e1a72dd9d72f6383f4f30fd342e21a8df32d984a4ea8f5eab691bb6ba6db2f8823d4b4cf135d98869e7a98925b81306aa32ee3c429f8cfa52c75889e1b + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.10, @babel/parser@npm:^7.22.5": + version: 7.22.10 + resolution: "@babel/parser@npm:7.22.10" + bin: + parser: ./bin/babel-parser.js + checksum: af51567b7d3cdf523bc608eae057397486c7fa6c2e5753027c01fe5c36f0767b2d01ce3049b222841326cc5b8c7fda1d810ac1a01af0a97bb04679e2ef9f7049 + languageName: node + linkType: hard + "@babel/parser@npm:^7.17.3, @babel/parser@npm:^7.18.10": version: 7.20.3 resolution: "@babel/parser@npm:7.20.3" @@ -135,6 +337,160 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7ed1c1d9b9e5b64ef028ea5e755c0be2d4e5e4e3d6cf7df757b9a8c4cfa4193d268176d0f1f7fbecdda6fe722885c7fda681f480f3741d8a2d26854736f05367 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3a10849d83e47aec50f367a9e56a6b22d662ddce643334b087f9828f4c3dd73bdc5909aaeabe123fed78515767f9ca43498a0e621c438d1cd2802d7fae3c9648 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.8.3": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": ^7.12.13 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 24f34b196d6342f28d4bad303612d7ff566ab0a013ce89e775d98d6f832969462e7235f3e7eaf17678a533d4be0ba45d3ae34ab4e5a9dcbda5d98d49e5efa2fc + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 166ac1125d10b9c0c430e4156249a13858c0366d38844883d75d27389621ebe651115cb2ceb6dc011534d5055719fa1727b59f39e1ab3ca97820eef3dcab5b9b + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bf5aea1f3188c9a507e16efe030efb996853ca3cadd6512c51db7233cc58f3ac89ff8c6bdfb01d30843b161cfe7d321e1bf28da82f7ab8d7e6bc5464666f354a + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.22.5 + resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8829d30c2617ab31393d99cec2978e41f014f4ac6f01a1cecf4c4dd8320c3ec12fdc3ce121126b2d8d32f6887e99ca1a0bad53dedb1e6ad165640b92b24980ce + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: aff33577037e34e515911255cdbb1fd39efee33658aa00b8a5fd3a4b903585112d037cce1cc9e4632f0487dc554486106b79ccd5ea63a2e00df4363f6d4ff886 + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 87aca4918916020d1fedba54c0e232de408df2644a425d153be368313fdde40d96088feed6c4e5ab72aac89be5d07fef2ddf329a15109c5eb65df006bf2580d1 + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 01ec5547bd0497f76cc903ff4d6b02abc8c05f301c88d2622b6d834e33a5651aa7c7a3d80d8d57656a4588f7276eba357f6b7e006482f5b564b7a6488de493a1 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: fddcf581a57f77e80eb6b981b10658421bc321ba5f0a5b754118c6a92a5448f12a0c336f77b8abf734841e102e5126d69110a306eadb03ca3e1547cab31f5cbf + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 910d90e72bc90ea1ce698e89c1027fed8845212d5ab588e35ef91f13b93143845f94e2539d831dc8d8ededc14ec02f04f7bd6a8179edd43a326c784e7ed7f0b9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: eef94d53a1453361553c1f98b68d17782861a04a392840341bc91780838dd4e695209c783631cf0de14c635758beafb6a3a65399846ffa4386bff90639347f30 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bbd1a56b095be7820029b209677b194db9b1d26691fe999856462e66b25b281f031f3dfd91b1619e9dcf95bebe336211833b854d0fb8780d618e35667c2d0d7e + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.22.5 + resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" + dependencies: + "@babel/helper-plugin-utils": ^7.22.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8ab7718fbb026d64da93681a57797d60326097fd7cb930380c8bffd9eb101689e90142c760a14b51e8e69c88a73ba3da956cb4520a3b0c65743aee5c71ef360a + languageName: node + linkType: hard + "@babel/runtime-corejs3@npm:^7.10.2": version: 7.18.6 resolution: "@babel/runtime-corejs3@npm:7.18.6" @@ -183,6 +539,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": + version: 7.22.5 + resolution: "@babel/template@npm:7.22.5" + dependencies: + "@babel/code-frame": ^7.22.5 + "@babel/parser": ^7.22.5 + "@babel/types": ^7.22.5 + checksum: c5746410164039aca61829cdb42e9a55410f43cace6f51ca443313f3d0bdfa9a5a330d0b0df73dc17ef885c72104234ae05efede37c1cc8a72dc9f93425977a3 + languageName: node + linkType: hard + "@babel/traverse@npm:7.17.3": version: 7.17.3 resolution: "@babel/traverse@npm:7.17.3" @@ -201,6 +568,24 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/traverse@npm:7.22.10" + dependencies: + "@babel/code-frame": ^7.22.10 + "@babel/generator": ^7.22.10 + "@babel/helper-environment-visitor": ^7.22.5 + "@babel/helper-function-name": ^7.22.5 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.22.10 + "@babel/types": ^7.22.10 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 9f7b358563bfb0f57ac4ed639f50e5c29a36b821a1ce1eea0c7db084f5b925e3275846d0de63bde01ca407c85d9804e0efbe370d92cd2baaafde3bd13b0f4cdb + languageName: node + linkType: hard + "@babel/types@npm:7.17.0": version: 7.17.0 resolution: "@babel/types@npm:7.17.0" @@ -211,6 +596,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3": + version: 7.22.10 + resolution: "@babel/types@npm:7.22.10" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.5 + to-fast-properties: ^2.0.0 + checksum: 095c4f4b7503fa816e4094113f0ec2351ef96ff32012010b771693066ff628c7c664b21c6bd3fb93aeb46fe7c61f6b3a3c9e4ed0034d6a2481201c417371c8af + languageName: node + linkType: hard + "@babel/types@npm:^7.17.0, @babel/types@npm:^7.18.10, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.2": version: 7.20.2 resolution: "@babel/types@npm:7.20.2" @@ -232,6 +628,13 @@ __metadata: languageName: node linkType: hard +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 850f9305536d0f2bd13e9e0881cb5f02e4f93fad1189f7b2d4bebf694e3206924eadee1068130d43c11b750efcc9405f88a8e42ef098b6d75239c0f047de1a27 + languageName: node + linkType: hard + "@blocto/sdk@npm:^0.2.22": version: 0.2.22 resolution: "@blocto/sdk@npm:0.2.22" @@ -925,6 +1328,7 @@ __metadata: "@solana/web3.js": ^1.77.0 "@tanstack/react-query": ^4.29.7 "@trivago/prettier-plugin-sort-imports": ^4.1.1 + "@types/jest": ^29.5.3 "@types/node": ^18.11.18 "@types/react": ^18.2.7 "@types/react-dom": ^18.2.4 @@ -938,6 +1342,7 @@ __metadata: eslint-config-prettier: ^8.8.0 ethers: ^5.7.2 formik: ^2.2.9 + jest: ^29.6.3 next: ^13.2.4 postcss: ^8.4.23 prettier: ^2.8.8 @@ -964,6 +1369,256 @@ __metadata: languageName: node linkType: hard +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: ^5.3.1 + find-up: ^4.1.0 + get-package-type: ^0.1.0 + js-yaml: ^3.13.1 + resolve-from: ^5.0.0 + checksum: d578da5e2e804d5c93228450a1380e1a3c691de4953acc162f387b717258512a3e07b83510a936d9fab03eac90817473917e24f5d16297af3867f59328d58568 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 5282759d961d61350f33d9118d16bcaed914ebf8061a52f4fa474b2cb08720c9c81d165e13b82f2e5a8a212cc5af482f0c6fc1ac27b9e067e5394c9a6ed186c9 + languageName: node + linkType: hard + +"@jest/console@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/console@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + jest-message-util: ^29.6.3 + jest-util: ^29.6.3 + slash: ^3.0.0 + checksum: a30b380166944ac06d36a50a36f05e65022b97064efd3ace7113d1dfc30d96966af578266f69817afa9d6ec679f8ceb6ae905352c07e5ad23d3c307fc0060174 + languageName: node + linkType: hard + +"@jest/core@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/core@npm:29.6.3" + dependencies: + "@jest/console": ^29.6.3 + "@jest/reporters": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/transform": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-changed-files: ^29.6.3 + jest-config: ^29.6.3 + jest-haste-map: ^29.6.3 + jest-message-util: ^29.6.3 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.6.3 + jest-resolve-dependencies: ^29.6.3 + jest-runner: ^29.6.3 + jest-runtime: ^29.6.3 + jest-snapshot: ^29.6.3 + jest-util: ^29.6.3 + jest-validate: ^29.6.3 + jest-watcher: ^29.6.3 + micromatch: ^4.0.4 + pretty-format: ^29.6.3 + slash: ^3.0.0 + strip-ansi: ^6.0.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 8ec37ce75f52dc85dfe703d4f8de31acf2134d1056127d075a700cf3668bad0cccc17f742b39f0053f8c12455075018bd3551093c0b3e082d593980093cb6ce9 + languageName: node + linkType: hard + +"@jest/environment@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/environment@npm:29.6.3" + dependencies: + "@jest/fake-timers": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + jest-mock: ^29.6.3 + checksum: 96aaf9baaa58fbacbdfbde9591297f25f9d6f5566cf10cd07d744a4a25b1d82b6cfb89f217a45ccce2cc50ec6c7e3c9a0122908d6b827985a1679afb5e10b7b1 + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/expect-utils@npm:29.6.3" + dependencies: + jest-get-type: ^29.6.3 + checksum: aeb0c2a485df09fdb51f866d58e232010cde888a7e6e1f9b395df236918e09e98407eb8281a3d41d2b115d9ff740d100b75100d521717ba903abeacb26e2a192 + languageName: node + linkType: hard + +"@jest/expect@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/expect@npm:29.6.3" + dependencies: + expect: ^29.6.3 + jest-snapshot: ^29.6.3 + checksum: 40c3fc53aa9f86e10129fcaec243405a4b4c398a8d65a3133f97d39331f065c3833c352b133377f003b2e9acc70909d72ac91698c219a883b857b7cda559b199 + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/fake-timers@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + "@sinonjs/fake-timers": ^10.0.2 + "@types/node": "*" + jest-message-util: ^29.6.3 + jest-mock: ^29.6.3 + jest-util: ^29.6.3 + checksum: 60be71159bb92c8b8da593fac2b2fff50c0760c26c3b17237561a2818382d3c797bd119a1707ec1d3e9b77e8e3d6513fe88f0c668d6ca26fb2c01ab475620888 + languageName: node + linkType: hard + +"@jest/globals@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/globals@npm:29.6.3" + dependencies: + "@jest/environment": ^29.6.3 + "@jest/expect": ^29.6.3 + "@jest/types": ^29.6.3 + jest-mock: ^29.6.3 + checksum: c90ad4e85c4c7fa42e4c61fc6bba854dc7e12c3579b4412fe879e712bf3675e92a771d2ac4ba2a48304a4dab34182e62e9d62f36ca13ddf8dff3cca911ddfbbb + languageName: node + linkType: hard + +"@jest/reporters@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/reporters@npm:29.6.3" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@jest/console": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/transform": ^29.6.3 + "@jest/types": ^29.6.3 + "@jridgewell/trace-mapping": ^0.3.18 + "@types/node": "*" + chalk: ^4.0.0 + collect-v8-coverage: ^1.0.0 + exit: ^0.1.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + istanbul-lib-coverage: ^3.0.0 + istanbul-lib-instrument: ^6.0.0 + istanbul-lib-report: ^3.0.0 + istanbul-lib-source-maps: ^4.0.0 + istanbul-reports: ^3.1.3 + jest-message-util: ^29.6.3 + jest-util: ^29.6.3 + jest-worker: ^29.6.3 + slash: ^3.0.0 + string-length: ^4.0.1 + strip-ansi: ^6.0.0 + v8-to-istanbul: ^9.0.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 8899240f018874148a24886ac78ada6dda4b7fc621fed904b276b324b981c2294d2036df92fb87411f2abb914faa351098eeb814d7685dcfa37c7c27b54660a4 + languageName: node + linkType: hard + +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 + languageName: node + linkType: hard + +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": ^0.3.18 + callsites: ^3.0.0 + graceful-fs: ^4.2.9 + checksum: bcc5a8697d471396c0003b0bfa09722c3cd879ad697eb9c431e6164e2ea7008238a01a07193dfe3cbb48b1d258eb7251f6efcea36f64e1ebc464ea3c03ae2deb + languageName: node + linkType: hard + +"@jest/test-result@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/test-result@npm:29.6.3" + dependencies: + "@jest/console": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/istanbul-lib-coverage": ^2.0.0 + collect-v8-coverage: ^1.0.0 + checksum: 0f8164520587555f4e0c5b3e0843ae8ae43c517301c2986b9ff24ca58215f407164b99f3ccfde778dc3fb299c3bb8922a3dd81cf3ccf0ff646806df61d3d2d78 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/test-sequencer@npm:29.6.3" + dependencies: + "@jest/test-result": ^29.6.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.3 + slash: ^3.0.0 + checksum: 71b5fee13e28b2006b4bdea62181dd6b7a537531ac027b1230ad96a5a0c7837a4c008e9cbeebee630b0c7cc22187fede48cb18fec79209ff641492c994db8259 + languageName: node + linkType: hard + +"@jest/transform@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/transform@npm:29.6.3" + dependencies: + "@babel/core": ^7.11.6 + "@jest/types": ^29.6.3 + "@jridgewell/trace-mapping": ^0.3.18 + babel-plugin-istanbul: ^6.1.1 + chalk: ^4.0.0 + convert-source-map: ^2.0.0 + fast-json-stable-stringify: ^2.1.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.3 + jest-regex-util: ^29.6.3 + jest-util: ^29.6.3 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.2 + checksum: edc47e960a71dab5ad8f0480fc4c1b05f2950c12e5aeb62bacfd46929dd5c7101dd2fa521a2e59c62a90849118039949f0230282a485de8dc373aac711f1bff9 + languageName: node + linkType: hard + +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": ^29.6.3 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc + languageName: node + linkType: hard + "@jnwng/walletconnect-solana@npm:^0.1.5": version: 0.1.5 resolution: "@jnwng/walletconnect-solana@npm:0.1.5" @@ -978,6 +1633,17 @@ __metadata: languageName: node linkType: hard +"@jridgewell/gen-mapping@npm:^0.3.0": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" + dependencies: + "@jridgewell/set-array": ^1.0.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -996,6 +1662,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.1 + resolution: "@jridgewell/resolve-uri@npm:3.1.1" + checksum: f5b441fe7900eab4f9155b3b93f9800a916257f4e8563afbcd3b5a5337b55e52bd8ae6735453b1b745457d9f6cdb16d74cd6220bbdd98cf153239e13f6cbb653 + languageName: node + linkType: hard + "@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" @@ -1010,6 +1683,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.14": + version: 1.4.15 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.15" + checksum: b881c7e503db3fc7f3c1f35a1dd2655a188cc51a3612d76efc8a6eb74728bef5606e6758ee77423e564092b4a518aba569bbb21c9bac5ab7a35b0c6ae7e344c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -1020,6 +1700,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18": + version: 0.3.19 + resolution: "@jridgewell/trace-mapping@npm:0.3.19" + dependencies: + "@jridgewell/resolve-uri": ^3.1.0 + "@jridgewell/sourcemap-codec": ^1.4.14 + checksum: 956a6f0f6fec060fb48c6bf1f5ec2064e13cd38c8be3873877d4b92b4a27ba58289a34071752671262a3e3c202abcc3fa2aac64d8447b4b0fa1ba3c9047f1c20 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.14 resolution: "@jridgewell/trace-mapping@npm:0.3.14" @@ -1719,6 +2409,31 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^3.0.0": + version: 3.0.0 + resolution: "@sinonjs/commons@npm:3.0.0" + dependencies: + type-detect: 4.0.8 + checksum: b4b5b73d4df4560fb8c0c7b38c7ad4aeabedd362f3373859d804c988c725889cde33550e4bcc7cd316a30f5152a2d1d43db71b6d0c38f5feef71fd8d016763f8 + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": ^3.0.0 + checksum: 614d30cb4d5201550c940945d44c9e0b6d64a888ff2cd5b357f95ad6721070d6b8839cd10e15b76bf5e14af0bcc1d8f9ec00d49a46318f1f669a4bec1d7f3148 + languageName: node + linkType: hard + "@socket.io/component-emitter@npm:~3.1.0": version: 3.1.0 resolution: "@socket.io/component-emitter@npm:3.1.0" @@ -3039,6 +3754,47 @@ __metadata: languageName: node linkType: hard +"@types/babel__core@npm:^7.1.14": + version: 7.20.1 + resolution: "@types/babel__core@npm:7.20.1" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: 9fcd9691a33074802d9057ff70b0e3ff3778f52470475b68698a0f6714fbe2ccb36c16b43dc924eb978cd8a81c1f845e5ff4699e7a47606043b539eb8c6331a8 + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.4 + resolution: "@types/babel__generator@npm:7.6.4" + dependencies: + "@babel/types": ^7.0.0 + checksum: 20effbbb5f8a3a0211e95959d06ae70c097fb6191011b73b38fe86deebefad8e09ee014605e0fd3cdaedc73d158be555866810e9166e1f09e4cfd880b874dcb0 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.1 + resolution: "@types/babel__template@npm:7.4.1" + dependencies: + "@babel/parser": ^7.1.0 + "@babel/types": ^7.0.0 + checksum: 649fe8b42c2876be1fd28c6ed9b276f78152d5904ec290b6c861d9ef324206e0a5c242e8305c421ac52ecf6358fa7e32ab7a692f55370484825c1df29b1596ee + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.20.1 + resolution: "@types/babel__traverse@npm:7.20.1" + dependencies: + "@babel/types": ^7.20.7 + checksum: 58341e23c649c0eba134a1682d4f20d027fad290d92e5740faa1279978f6ed476fc467ae51ce17a877e2566d805aeac64eae541168994367761ec883a4150221 + languageName: node + linkType: hard + "@types/coingecko-api@npm:^1.0.10": version: 1.0.10 resolution: "@types/coingecko-api@npm:1.0.10" @@ -3064,6 +3820,50 @@ __metadata: languageName: node linkType: hard +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" + dependencies: + "@types/node": "*" + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 + languageName: node + linkType: hard + +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.4 + resolution: "@types/istanbul-lib-coverage@npm:2.0.4" + checksum: a25d7589ee65c94d31464c16b72a9dc81dfa0bea9d3e105ae03882d616e2a0712a9c101a599ec482d297c3591e16336962878cb3eb1a0a62d5b76d277a890ce7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.0 + resolution: "@types/istanbul-lib-report@npm:3.0.0" + dependencies: + "@types/istanbul-lib-coverage": "*" + checksum: 656398b62dc288e1b5226f8880af98087233cdb90100655c989a09f3052b5775bf98ba58a16c5ae642fb66c61aba402e07a9f2bff1d1569e3b306026c59f3f36 + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/istanbul-reports@npm:3.0.1" + dependencies: + "@types/istanbul-lib-report": "*" + checksum: f1ad54bc68f37f60b30c7915886b92f86b847033e597f9b34f2415acdbe5ed742fa559a0a40050d74cdba3b6a63c342cac1f3a64dba5b68b66a6941f4abd7903 + languageName: node + linkType: hard + +"@types/jest@npm:^29.5.3": + version: 29.5.3 + resolution: "@types/jest@npm:29.5.3" + dependencies: + expect: ^29.0.0 + pretty-format: ^29.0.0 + checksum: e36bb92e0b9e5ea7d6f8832baa42f087fc1697f6cd30ec309a07ea4c268e06ec460f1f0cfd2581daf5eff5763475190ec1ad8ac6520c49ccfe4f5c0a48bfa676 + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" @@ -3158,6 +3958,13 @@ __metadata: languageName: node linkType: hard +"@types/stack-utils@npm:^2.0.0": + version: 2.0.1 + resolution: "@types/stack-utils@npm:2.0.1" + checksum: 205fdbe3326b7046d7eaf5e494d8084f2659086a266f3f9cf00bccc549c8e36e407f88168ad4383c8b07099957ad669f75f2532ed4bc70be2b037330f7bae019 + languageName: node + linkType: hard + "@types/trusted-types@npm:^2.0.2": version: 2.0.3 resolution: "@types/trusted-types@npm:2.0.3" @@ -3174,6 +3981,22 @@ __metadata: languageName: node linkType: hard +"@types/yargs-parser@npm:*": + version: 21.0.0 + resolution: "@types/yargs-parser@npm:21.0.0" + checksum: b2f4c8d12ac18a567440379909127cf2cec393daffb73f246d0a25df36ea983b93b7e9e824251f959e9f928cbc7c1aab6728d0a0ff15d6145f66cec2be67d9a2 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" + dependencies: + "@types/yargs-parser": "*" + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^5.59.7": version: 5.59.7 resolution: "@typescript-eslint/eslint-plugin@npm:5.59.7" @@ -4278,6 +5101,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + languageName: node + linkType: hard + "ansi-regex@npm:^4.1.0": version: 4.1.1 resolution: "ansi-regex@npm:4.1.1" @@ -4310,6 +5142,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -4317,6 +5156,16 @@ __metadata: languageName: node linkType: hard +"anymatch@npm:^3.0.3": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: ^3.0.0 + picomatch: ^2.0.4 + checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 + languageName: node + linkType: hard + "anymatch@npm:~3.1.2": version: 3.1.2 resolution: "anymatch@npm:3.1.2" @@ -4358,6 +5207,15 @@ __metadata: languageName: node linkType: hard +"argparse@npm:^1.0.7": + version: 1.0.10 + resolution: "argparse@npm:1.0.10" + dependencies: + sprintf-js: ~1.0.2 + checksum: 7ca6e45583a28de7258e39e13d81e925cfa25d7d4aacbf806a382d3c02fcb13403a07fb8aeef949f10a7cfe4a62da0e2e807b348a5980554cc28ee573ef95945 + languageName: node + linkType: hard + "argparse@npm:^2.0.1": version: 2.0.1 resolution: "argparse@npm:2.0.1" @@ -4523,6 +5381,82 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-jest@npm:29.6.3" + dependencies: + "@jest/transform": ^29.6.3 + "@types/babel__core": ^7.1.14 + babel-plugin-istanbul: ^6.1.1 + babel-preset-jest: ^29.6.3 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + slash: ^3.0.0 + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 8b4b85d829d8ee010f0c8381cb9d67842da905c32183c1fc6e1e8833447a79b969f8279759d44197bb77001239dc41a49fff0e8222d8e8577f47a8d0428d178e + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": ^7.0.0 + "@istanbuljs/load-nyc-config": ^1.0.0 + "@istanbuljs/schema": ^0.1.2 + istanbul-lib-instrument: ^5.0.4 + test-exclude: ^6.0.0 + checksum: cb4fd95738219f232f0aece1116628cccff16db891713c4ccb501cddbbf9272951a5df81f2f2658dfdf4b3e7b236a9d5cbcf04d5d8c07dd5077297339598061a + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": ^7.3.3 + "@babel/types": ^7.3.3 + "@types/babel__core": ^7.1.14 + "@types/babel__traverse": ^7.0.6 + checksum: 51250f22815a7318f17214a9d44650ba89551e6d4f47a2dc259128428324b52f5a73979d010cefd921fd5a720d8c1d55ad74ff601cd94c7bd44d5f6292fde2d1 + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.0.1 + resolution: "babel-preset-current-node-syntax@npm:1.0.1" + dependencies: + "@babel/plugin-syntax-async-generators": ^7.8.4 + "@babel/plugin-syntax-bigint": ^7.8.3 + "@babel/plugin-syntax-class-properties": ^7.8.3 + "@babel/plugin-syntax-import-meta": ^7.8.3 + "@babel/plugin-syntax-json-strings": ^7.8.3 + "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + "@babel/plugin-syntax-numeric-separator": ^7.8.3 + "@babel/plugin-syntax-object-rest-spread": ^7.8.3 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + "@babel/plugin-syntax-top-level-await": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: d118c2742498c5492c095bc8541f4076b253e705b5f1ad9a2e7d302d81a84866f0070346662355c8e25fc02caa28dc2da8d69bcd67794a0d60c4d6fab6913cc8 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: ^29.6.3 + babel-preset-current-node-syntax: ^1.0.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: aa4ff2a8a728d9d698ed521e3461a109a1e66202b13d3494e41eea30729a5e7cc03b3a2d56c594423a135429c37bf63a9fa8b0b9ce275298be3095a88c69f6fb + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -4752,6 +5686,20 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.21.9": + version: 4.21.10 + resolution: "browserslist@npm:4.21.10" + dependencies: + caniuse-lite: ^1.0.30001517 + electron-to-chromium: ^1.4.477 + node-releases: ^2.0.13 + update-browserslist-db: ^1.0.11 + bin: + browserslist: cli.js + checksum: 1e27c0f111a35d1dd0e8fc2c61781b0daefabc2c9471b0b10537ce54843014bceb2a1ce4571af1a82b2bf1e6e6e05d38865916689a158f03bc2c7a4ec2577db8 + languageName: node + linkType: hard + "bs58@npm:^4.0.0, bs58@npm:^4.0.1": version: 4.0.1 resolution: "bs58@npm:4.0.1" @@ -4781,6 +5729,15 @@ __metadata: languageName: node linkType: hard +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: ^0.4.0 + checksum: 9ba4dc58ce86300c862bffc3ae91f00b2a03b01ee07f3564beeeaf82aa243b8b03ba53f123b0b842c190d4399b94697970c8e7cf7b1ea44b61aa28c3526a4449 + languageName: node + linkType: hard + "buffer-alloc-unsafe@npm:^1.1.0": version: 1.1.0 resolution: "buffer-alloc-unsafe@npm:1.1.0" @@ -4805,7 +5762,7 @@ __metadata: languageName: node linkType: hard -"buffer-from@npm:^1.1.1": +"buffer-from@npm:^1.0.0, buffer-from@npm:^1.1.1": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb @@ -4909,13 +5866,20 @@ __metadata: languageName: node linkType: hard -"camelcase@npm:^5.0.0": +"camelcase@npm:^5.0.0, camelcase@npm:^5.3.1": version: 5.3.1 resolution: "camelcase@npm:5.3.1" checksum: e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b languageName: node linkType: hard +"camelcase@npm:^6.2.0": + version: 6.3.0 + resolution: "camelcase@npm:6.3.0" + checksum: 8c96818a9076434998511251dcb2761a94817ea17dbdc37f47ac080bd088fc62c7369429a19e2178b993497132c8cbcf5cc1f44ba963e76782ba469c0474938d + languageName: node + linkType: hard + "caniuse-lite@npm:^1.0.30001406": version: 1.0.30001418 resolution: "caniuse-lite@npm:1.0.30001418" @@ -4930,6 +5894,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001517": + version: 1.0.30001522 + resolution: "caniuse-lite@npm:1.0.30001522" + checksum: 56e3551c02ae595085114073cf242f7d9d54d32255c80893ca9098a44f44fc6eef353936f234f31c7f4cb894dd2b6c9c4626e30649ee29e04d70aa127eeefeb0 + languageName: node + linkType: hard + "cbor-sync@npm:^1.0.4": version: 1.0.4 resolution: "cbor-sync@npm:1.0.4" @@ -4937,7 +5908,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0": +"chalk@npm:^2.0.0, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -4958,6 +5929,13 @@ __metadata: languageName: node linkType: hard +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: b563e4b6039b15213114626621e7a3d12f31008bdce20f9c741d69987f62aeaace7ec30f6018890ad77b2e9b4d95324c9f5acfca58a9441e3b1dcdd1e2525d17 + languageName: node + linkType: hard + "chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -4984,6 +5962,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^3.2.0": + version: 3.8.0 + resolution: "ci-info@npm:3.8.0" + checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 + languageName: node + linkType: hard + "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": version: 1.0.4 resolution: "cipher-base@npm:1.0.4" @@ -4994,6 +5979,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.0.0": + version: 1.2.3 + resolution: "cjs-module-lexer@npm:1.2.3" + checksum: 5ea3cb867a9bb609b6d476cd86590d105f3cfd6514db38ff71f63992ab40939c2feb68967faa15a6d2b1f90daa6416b79ea2de486e9e2485a6f8b66a21b4fb0a + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -5030,6 +6022,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "clsx@npm:1.1.1": version: 1.1.1 resolution: "clsx@npm:1.1.1" @@ -5044,6 +6047,13 @@ __metadata: languageName: node linkType: hard +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 5210d9223010eb95b29df06a91116f2cf7c8e0748a9013ed853b53f362ea0e822f1e5bb054fb3cefc645239a4cf966af1f6133a3b43f40d591f3b68ed6cf0510 + languageName: node + linkType: hard + "coingecko-api@npm:^1.0.10": version: 1.0.10 resolution: "coingecko-api@npm:1.0.10" @@ -5051,6 +6061,13 @@ __metadata: languageName: node linkType: hard +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.2 + resolution: "collect-v8-coverage@npm:1.0.2" + checksum: c10f41c39ab84629d16f9f6137bc8a63d332244383fc368caf2d2052b5e04c20cd1fd70f66fcf4e2422b84c8226598b776d39d5f2d2a51867cc1ed5d1982b4da + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -5120,6 +6137,20 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 63ae9933be5a2b8d4509daca5124e20c14d023c820258e484e32dc324d34c2754e71297c94a05784064ad27615037ef677e3f0c00469fb55f409d2bb21261035 + languageName: node + linkType: hard + "copy-to-clipboard@npm:^3.3.1, copy-to-clipboard@npm:^3.3.3": version: 3.3.3 resolution: "copy-to-clipboard@npm:3.3.3" @@ -5316,6 +6347,18 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^1.0.0": + version: 1.5.1 + resolution: "dedent@npm:1.5.1" + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + checksum: c3c300a14edf1bdf5a873f9e4b22e839d62490bc5c8d6169c1f15858a1a76733d06a9a56930e963d677a2ceeca4b6b0894cc5ea2f501aa382ca5b92af3413c2a + languageName: node + linkType: hard + "deep-is@npm:^0.1.3": version: 0.1.4 resolution: "deep-is@npm:0.1.4" @@ -5406,6 +6449,13 @@ __metadata: languageName: node linkType: hard +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 + languageName: node + linkType: hard + "detect-node-es@npm:^1.1.0": version: 1.1.0 resolution: "detect-node-es@npm:1.1.0" @@ -5420,6 +6470,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: f4914158e1f2276343d98ff5b31fc004e7304f5470bf0f1adb2ac6955d85a531a6458d33e87667f98f6ae52ebd3891bb47d420bb48a5bd8b7a27ee25b20e33aa + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -5507,6 +6564,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.477": + version: 1.4.498 + resolution: "electron-to-chromium@npm:1.4.498" + checksum: 01962ae42e9097c321cb6ff63ca97dfd36457050727893d1768e6eb1b7d5a48ece568b94b1128fd0211f7ce3a31aca0c17eb72b1292d9b5ef7b0664d90dfe3aa + languageName: node + linkType: hard + "elliptic@npm:6.5.4, elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" @@ -5522,6 +6586,13 @@ __metadata: languageName: node linkType: hard +"emittery@npm:^0.13.1": + version: 0.13.1 + resolution: "emittery@npm:0.13.1" + checksum: 2b089ab6306f38feaabf4f6f02792f9ec85fc054fda79f44f6790e61bbf6bc4e1616afb9b232e0c5ec5289a8a452f79bfa6d905a6fd64e94b49981f0934001c6 + languageName: node + linkType: hard + "emoji-regex@npm:^7.0.1": version: 7.0.3 resolution: "emoji-regex@npm:7.0.3" @@ -5612,6 +6683,15 @@ __metadata: languageName: node linkType: hard +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: ^0.2.1 + checksum: c1c2b8b65f9c91b0f9d75f0debaa7ec5b35c266c2cac5de412c1a6de86d4cbae04ae44e510378cb14d032d0645a36925d0186f8bb7367bcc629db256b743a001 + languageName: node + linkType: hard + "es-abstract@npm:^1.19.0, es-abstract@npm:^1.19.1, es-abstract@npm:^1.19.2, es-abstract@npm:^1.19.5, es-abstract@npm:^1.20.0": version: 1.20.1 resolution: "es-abstract@npm:1.20.1" @@ -5700,6 +6780,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 9f8a2d5743677c16e85c810e3024d54f0c8dea6424fad3c79ef6666e81dd0846f7437f5e729dfcdac8981bc9e5294c39b4580814d114076b8d36318f46ae4395 + languageName: node + linkType: hard + "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -5952,6 +7039,16 @@ __metadata: languageName: node linkType: hard +"esprima@npm:^4.0.0": + version: 4.0.1 + resolution: "esprima@npm:4.0.1" + bin: + esparse: ./bin/esparse.js + esvalidate: ./bin/esvalidate.js + checksum: b45bc805a613dbea2835278c306b91aff6173c8d034223fa81498c77dcbce3b2931bf6006db816f62eacd9fd4ea975dfd85a5b7f3c6402cfd050d4ca3c13a628 + languageName: node + linkType: hard + "esquery@npm:^1.4.2": version: 1.5.0 resolution: "esquery@npm:1.5.0" @@ -6126,6 +7223,23 @@ __metadata: languageName: node linkType: hard +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.0 + human-signals: ^2.1.0 + is-stream: ^2.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^4.0.1 + onetime: ^5.1.2 + signal-exit: ^3.0.3 + strip-final-newline: ^2.0.0 + checksum: fba9022c8c8c15ed862847e94c252b3d946036d7547af310e344a527e59021fd8b6bb0723883ea87044dc4f0201f949046993124a42ccb0855cae5bf8c786343 + languageName: node + linkType: hard + "exenv@npm:^1.2.0": version: 1.2.2 resolution: "exenv@npm:1.2.2" @@ -6133,6 +7247,26 @@ __metadata: languageName: node linkType: hard +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: abc407f07a875c3961e4781dfcb743b58d6c93de9ab263f4f8c9d23bb6da5f9b7764fc773f86b43dd88030444d5ab8abcb611cb680fba8ca075362b77114bba3 + languageName: node + linkType: hard + +"expect@npm:^29.0.0, expect@npm:^29.6.3": + version: 29.6.3 + resolution: "expect@npm:29.6.3" + dependencies: + "@jest/expect-utils": ^29.6.3 + jest-get-type: ^29.6.3 + jest-matcher-utils: ^29.6.3 + jest-message-util: ^29.6.3 + jest-util: ^29.6.3 + checksum: c72de87abbc9acc17c66f42fcac8be4dff256f871f1800c3aaa004c74f95f61866cf80e8f2ddacc3f2df290fd58b0cba8adb3a0dee3a09dd5d39f97f63d2aae8 + languageName: node + linkType: hard + "eyes@npm:^0.1.8": version: 0.1.8 resolution: "eyes@npm:0.1.8" @@ -6173,7 +7307,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -6217,6 +7351,15 @@ __metadata: languageName: node linkType: hard +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: 2.1.1 + checksum: b15a124cef28916fe07b400eb87cbc73ca082c142abf7ca8e8de6af43eca79ca7bd13eb4d4d48240b3bd3136eaac40d16e42d6edf87a8e5d1dd8070626860c78 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -6267,7 +7410,7 @@ __metadata: languageName: node linkType: hard -"find-up@npm:^4.1.0": +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": version: 4.1.0 resolution: "find-up@npm:4.1.0" dependencies: @@ -6363,6 +7506,16 @@ __metadata: languageName: node linkType: hard +"fsevents@npm:^2.3.2": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: latest + checksum: 11e6ea6fea15e42461fc55b4b0e4a0a3c654faa567f1877dbd353f39156f69def97a69936d1746619d656c4b93de2238bf731f6085a03a50cabf287c9d024317 + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" @@ -6373,6 +7526,15 @@ __metadata: languageName: node linkType: hard +"fsevents@patch:fsevents@^2.3.2#~builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#~builtin::version=2.3.3&hash=18f3a7" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + "fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=18f3a7" @@ -6424,7 +7586,14 @@ __metadata: languageName: node linkType: hard -"get-caller-file@npm:^2.0.1": +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.1, get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 @@ -6449,6 +7618,20 @@ __metadata: languageName: node linkType: hard +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad + languageName: node + linkType: hard + "get-symbol-description@npm:^1.0.0": version: 1.0.0 resolution: "get-symbol-description@npm:1.0.0" @@ -6603,6 +7786,13 @@ __metadata: languageName: node linkType: hard +"graceful-fs@npm:^4.2.9": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 + languageName: node + linkType: hard + "grapheme-splitter@npm:^1.0.4": version: 1.0.4 resolution: "grapheme-splitter@npm:1.0.4" @@ -6727,6 +7917,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: d2df2da3ad40ca9ee3a39c5cc6475ef67c8f83c234475f24d8e9ce0dc80a2c82df8e1d6fa78ddd1e9022a586ea1bd247a615e80a5cd9273d90111ddda7d9e974 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.0": version: 4.1.0 resolution: "http-cache-semantics@npm:4.1.0" @@ -6755,6 +7952,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: b87fd89fce72391625271454e70f67fe405277415b48bcc0117ca73d31fa23a4241787afdc8d67f5a116cf37258c052f59ea82daffa72364d61351423848e3b8 + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -6797,6 +8001,18 @@ __metadata: languageName: node linkType: hard +"import-local@npm:^3.0.2": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: ^4.2.0 + resolve-cwd: ^3.0.0 + bin: + import-local-fixture: fixtures/cli.js + checksum: bfcdb63b5e3c0e245e347f3107564035b128a414c4da1172a20dc67db2504e05ede4ac2eee1252359f78b0bfd7b19ef180aec427c2fce6493ae782d73a04cddd + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -6872,6 +8088,13 @@ __metadata: languageName: node linkType: hard +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: eef4417e3c10e60e2c810b6084942b3ead455af16c4509959a27e490e7aee87cfb3f38e01bbde92220b528a0ee1a18d52b787e1458ee86174d8c7f0e58cd488f + languageName: node + linkType: hard + "is-bigint@npm:^1.0.1": version: 1.0.4 resolution: "is-bigint@npm:1.0.4" @@ -6973,6 +8196,13 @@ __metadata: languageName: node linkType: hard +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 + languageName: node + linkType: hard + "is-generator-function@npm:^1.0.7": version: 1.0.10 resolution: "is-generator-function@npm:1.0.10" @@ -7064,7 +8294,7 @@ __metadata: languageName: node linkType: hard -"is-stream@npm:^2.0.1": +"is-stream@npm:^2.0.0, is-stream@npm:^2.0.1": version: 2.0.1 resolution: "is-stream@npm:2.0.1" checksum: b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 @@ -7150,6 +8380,71 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-lib-coverage@npm:3.2.0" + checksum: a2a545033b9d56da04a8571ed05c8120bf10e9bce01cf8633a3a2b0d1d83dff4ac4fe78d6d5673c27fc29b7f21a41d75f83a36be09f82a61c367b56aa73c1ff9 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": ^7.12.3 + "@babel/parser": ^7.14.7 + "@istanbuljs/schema": ^0.1.2 + istanbul-lib-coverage: ^3.2.0 + semver: ^6.3.0 + checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^6.0.0": + version: 6.0.0 + resolution: "istanbul-lib-instrument@npm:6.0.0" + dependencies: + "@babel/core": ^7.12.3 + "@babel/parser": ^7.14.7 + "@istanbuljs/schema": ^0.1.2 + istanbul-lib-coverage: ^3.2.0 + semver: ^7.5.4 + checksum: b9dc3723a769e65dbe1b912f935088ffc07cf393fa78a3ce79022c91aabb0ad01405ffd56083cdd822e514798e9daae3ea7bfe85633b094ecb335d28eb0a3f97 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: ^3.0.0 + make-dir: ^4.0.0 + supports-color: ^7.1.0 + checksum: fd17a1b879e7faf9bb1dc8f80b2a16e9f5b7b8498fe6ed580a618c34df0bfe53d2abd35bf8a0a00e628fb7405462576427c7df20bbe4148d19c14b431c974b21 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: ^4.1.1 + istanbul-lib-coverage: ^3.0.0 + source-map: ^0.6.1 + checksum: 21ad3df45db4b81852b662b8d4161f6446cd250c1ddc70ef96a585e2e85c26ed7cd9c2a396a71533cfb981d1a645508bc9618cae431e55d01a0628e7dec62ef2 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.6 + resolution: "istanbul-reports@npm:3.1.6" + dependencies: + html-escaper: ^2.0.0 + istanbul-lib-report: ^3.0.0 + checksum: 44c4c0582f287f02341e9720997f9e82c071627e1e862895745d5f52ec72c9b9f38e1d12370015d2a71dcead794f34c7732aaef3fab80a24bc617a21c3d911d6 + languageName: node + linkType: hard + "javascript-natural-sort@npm:0.7.1": version: 0.7.1 resolution: "javascript-natural-sort@npm:0.7.1" @@ -7180,25 +8475,465 @@ __metadata: languageName: node linkType: hard -"jayson@npm:^4.1.0": - version: 4.1.0 - resolution: "jayson@npm:4.1.0" +"jayson@npm:^4.1.0": + version: 4.1.0 + resolution: "jayson@npm:4.1.0" + dependencies: + "@types/connect": ^3.4.33 + "@types/node": ^12.12.54 + "@types/ws": ^7.4.4 + JSONStream: ^1.3.5 + commander: ^2.20.3 + delay: ^5.0.0 + es6-promisify: ^5.0.0 + eyes: ^0.1.8 + isomorphic-ws: ^4.0.1 + json-stringify-safe: ^5.0.1 + uuid: ^8.3.2 + ws: ^7.4.5 + bin: + jayson: bin/jayson.js + checksum: 86464322fbdc6db65d2bb4fc278cb6c86fad5c2a506065490d39459f09ba0d30f2b4fb740b33828a1424791419b6c8bd295dc54d361a4ad959bf70cc62b1ca7e + languageName: node + linkType: hard + +"jest-changed-files@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-changed-files@npm:29.6.3" + dependencies: + execa: ^5.0.0 + jest-util: ^29.6.3 + p-limit: ^3.1.0 + checksum: 55bc820a70c220a02fec214d5c48d5e0d829549e5c7b9959776b4ca3f76f5ff20c7c8ff816a847822766f1d712477ab3027f7a66ec61bf65de3f852e878b4dfd + languageName: node + linkType: hard + +"jest-circus@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-circus@npm:29.6.3" + dependencies: + "@jest/environment": ^29.6.3 + "@jest/expect": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + co: ^4.6.0 + dedent: ^1.0.0 + is-generator-fn: ^2.0.0 + jest-each: ^29.6.3 + jest-matcher-utils: ^29.6.3 + jest-message-util: ^29.6.3 + jest-runtime: ^29.6.3 + jest-snapshot: ^29.6.3 + jest-util: ^29.6.3 + p-limit: ^3.1.0 + pretty-format: ^29.6.3 + pure-rand: ^6.0.0 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 65b76f853d1bd2ddc74ec5d9a37cff3d04d436e675b0ded52167ba9e5dfb9d6fbca8572c9f255d379ad332e87770bac3da6dbcabcaf840ee2ba6e0cde5b8c20e + languageName: node + linkType: hard + +"jest-cli@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-cli@npm:29.6.3" + dependencies: + "@jest/core": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/types": ^29.6.3 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + import-local: ^3.0.2 + jest-config: ^29.6.3 + jest-util: ^29.6.3 + jest-validate: ^29.6.3 + prompts: ^2.0.1 + yargs: ^17.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 69c422f1522b25756afb5a27b4b01a710d0f5ba52c592903b1ab47103ee2414ac9a9fff36a976092bb595980ba5c45f128e33b5d6ebc666c8a6973474bbf1443 + languageName: node + linkType: hard + +"jest-config@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-config@npm:29.6.3" + dependencies: + "@babel/core": ^7.11.6 + "@jest/test-sequencer": ^29.6.3 + "@jest/types": ^29.6.3 + babel-jest: ^29.6.3 + chalk: ^4.0.0 + ci-info: ^3.2.0 + deepmerge: ^4.2.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-circus: ^29.6.3 + jest-environment-node: ^29.6.3 + jest-get-type: ^29.6.3 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.6.3 + jest-runner: ^29.6.3 + jest-util: ^29.6.3 + jest-validate: ^29.6.3 + micromatch: ^4.0.4 + parse-json: ^5.2.0 + pretty-format: ^29.6.3 + slash: ^3.0.0 + strip-json-comments: ^3.1.1 + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: c3505411b89e5d046fbd294bb6e9ccc8c64a7efcf9d546450bec25512db4cbb67c8d102e4a58fa8ef8eac73052d1259533d9012b483469581ad5ed4cc5faa39f + languageName: node + linkType: hard + +"jest-diff@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-diff@npm:29.6.3" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.6.3 + jest-get-type: ^29.6.3 + pretty-format: ^29.6.3 + checksum: 23b0a88efeab36566386f059f3da340754d2860969cbc34805154e2377714e37e3130e21a791fc68008fb460bbf5edd7ec43c16d96d15797b32ccfae5160fe37 + languageName: node + linkType: hard + +"jest-docblock@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-docblock@npm:29.6.3" + dependencies: + detect-newline: ^3.0.0 + checksum: 6f3213a1e79e7eedafeb462acfa9a41303f9c0167893b140f6818fa16d7eb6bf3f9b9cf4669097ca6b7154847793489ecd6b4f6cfb0e416b88cfa3b4b36715b6 + languageName: node + linkType: hard + +"jest-each@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-each@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + chalk: ^4.0.0 + jest-get-type: ^29.6.3 + jest-util: ^29.6.3 + pretty-format: ^29.6.3 + checksum: fe06e80b3554e2a8464f5f5c61943e02db1f8a7177139cb55b3201a1d1513cb089d8800401f102729a31bf8dd6f88229044e6088fea9dd5647ed11e841b6b88c + languageName: node + linkType: hard + +"jest-environment-node@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-environment-node@npm:29.6.3" + dependencies: + "@jest/environment": ^29.6.3 + "@jest/fake-timers": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + jest-mock: ^29.6.3 + jest-util: ^29.6.3 + checksum: c215d8d94d95ba0353677c8b6c7c46d3f612bfd6becafa90e842ab99cb4ba2243c7f0309f1518ea2879820d39c0f3ec0d678e9ebb41055ed6eedbeb123f2897c + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205 + languageName: node + linkType: hard + +"jest-haste-map@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-haste-map@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + "@types/graceful-fs": ^4.1.3 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^29.6.3 + jest-util: ^29.6.3 + jest-worker: ^29.6.3 + micromatch: ^4.0.4 + walker: ^1.0.8 + dependenciesMeta: + fsevents: + optional: true + checksum: d72b81442cf54c5962009502b4001e53b7e40ecd1717bb5d17d5b0badc89cf5529b8be5d2804442d25ee6a70809de150e554b074029170b0e86a32b7560ce430 + languageName: node + linkType: hard + +"jest-leak-detector@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-leak-detector@npm:29.6.3" + dependencies: + jest-get-type: ^29.6.3 + pretty-format: ^29.6.3 + checksum: 27548fcfc7602fe1b88f8600185e35ffff71751f3631e52bbfdfc72776f5a13a430185cf02fc632b41320a74f99ae90e40ce101c8887509f0f919608a7175129 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-matcher-utils@npm:29.6.3" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.6.3 + jest-get-type: ^29.6.3 + pretty-format: ^29.6.3 + checksum: d4965d5cc079799bc0a9075daea7a964768d4db55f0388ef879642215399c955ae9a22c967496813c908763b487f97e40701a1eb4ed5b0b7529c447b6d33e652 + languageName: node + linkType: hard + +"jest-message-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-message-util@npm:29.6.3" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^29.6.3 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^29.6.3 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 59f5229a06c073a8877ba4d2e304cc07d63b0062bf5764d4bed14364403889e77f1825d1bd9017c19a840847d17dffd414dc06f1fcb537b5f9e03dbc65b84ada + languageName: node + linkType: hard + +"jest-mock@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-mock@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + "@types/node": "*" + jest-util: ^29.6.3 + checksum: 35772968010c0afb1bb1ef78570b9cbea907c6f967d24b4e95e1a596a1000c63d60e225fb9ddfdd5218674da4aa61d92a09927fc26310cecbbfaa8278d919e32 + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2 + languageName: node + linkType: hard + +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-resolve-dependencies@npm:29.6.3" + dependencies: + jest-regex-util: ^29.6.3 + jest-snapshot: ^29.6.3 + checksum: db0e57158cc085926f1e0dd63919cc78b87dc7e5644cd40f6b4b0bdcc228f3872b5520477db9a67889f4bcf658c5b85303fef89eee1df60d02a662c356021c2f + languageName: node + linkType: hard + +"jest-resolve@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-resolve@npm:29.6.3" + dependencies: + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.3 + jest-pnp-resolver: ^1.2.2 + jest-util: ^29.6.3 + jest-validate: ^29.6.3 + resolve: ^1.20.0 + resolve.exports: ^2.0.0 + slash: ^3.0.0 + checksum: 94594aab55b957e4f13fec248a18c99a6d8eb4842aa33ea5ef77179604df206d3fff1c59393a8984f179d0a7c6b98322d260b356076cdc2e74f2ebf1d9fba74a + languageName: node + linkType: hard + +"jest-runner@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-runner@npm:29.6.3" + dependencies: + "@jest/console": ^29.6.3 + "@jest/environment": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/transform": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + emittery: ^0.13.1 + graceful-fs: ^4.2.9 + jest-docblock: ^29.6.3 + jest-environment-node: ^29.6.3 + jest-haste-map: ^29.6.3 + jest-leak-detector: ^29.6.3 + jest-message-util: ^29.6.3 + jest-resolve: ^29.6.3 + jest-runtime: ^29.6.3 + jest-util: ^29.6.3 + jest-watcher: ^29.6.3 + jest-worker: ^29.6.3 + p-limit: ^3.1.0 + source-map-support: 0.5.13 + checksum: 9f10100f1a558ec78d24e131494d9b3736633f788f3edcd30dbce7257c0cee6f62fec08ab99dbb684ddcc7dbb5ca846711b140ca6090a9547c5900a0e3da53f8 + languageName: node + linkType: hard + +"jest-runtime@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-runtime@npm:29.6.3" + dependencies: + "@jest/environment": ^29.6.3 + "@jest/fake-timers": ^29.6.3 + "@jest/globals": ^29.6.3 + "@jest/source-map": ^29.6.3 + "@jest/test-result": ^29.6.3 + "@jest/transform": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + cjs-module-lexer: ^1.0.0 + collect-v8-coverage: ^1.0.0 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.3 + jest-message-util: ^29.6.3 + jest-mock: ^29.6.3 + jest-regex-util: ^29.6.3 + jest-resolve: ^29.6.3 + jest-snapshot: ^29.6.3 + jest-util: ^29.6.3 + slash: ^3.0.0 + strip-bom: ^4.0.0 + checksum: 8743c61a2354dbce87282bfcbc11049f7d30d25ecd5f475ce56c1b7d926debb21b04db284d4d65a14283893a696442c66e923b35742fb02cc9f940a0a41ca49e + languageName: node + linkType: hard + +"jest-snapshot@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-snapshot@npm:29.6.3" + dependencies: + "@babel/core": ^7.11.6 + "@babel/generator": ^7.7.2 + "@babel/plugin-syntax-jsx": ^7.7.2 + "@babel/plugin-syntax-typescript": ^7.7.2 + "@babel/types": ^7.3.3 + "@jest/expect-utils": ^29.6.3 + "@jest/transform": ^29.6.3 + "@jest/types": ^29.6.3 + babel-preset-current-node-syntax: ^1.0.0 + chalk: ^4.0.0 + expect: ^29.6.3 + graceful-fs: ^4.2.9 + jest-diff: ^29.6.3 + jest-get-type: ^29.6.3 + jest-matcher-utils: ^29.6.3 + jest-message-util: ^29.6.3 + jest-util: ^29.6.3 + natural-compare: ^1.4.0 + pretty-format: ^29.6.3 + semver: ^7.5.3 + checksum: c63631d2c18adc678455b9aa6e569cb1ea227e97aaa8628e154b39c95ca626d89e88d62c82e07d66cc83a1fddda1f7153506dd0f49d3411bbbecb52272ed72f5 + languageName: node + linkType: hard + +"jest-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-util@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: 7bf3ba3ac67ac6ceff7d8fdd23a86768e23ddd9133ecd9140ef87cc0c28708effabaf67a6cd45cd9d90a63d645a522ed0825d09ee59ac4c03b9c473b1fef4c7c + languageName: node + linkType: hard + +"jest-validate@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-validate@npm:29.6.3" + dependencies: + "@jest/types": ^29.6.3 + camelcase: ^6.2.0 + chalk: ^4.0.0 + jest-get-type: ^29.6.3 + leven: ^3.1.0 + pretty-format: ^29.6.3 + checksum: caa489ed11080441c636b8035ab71bafbdc0c052b1e452855e4d2dd24ac15e497710a270ea6fc5ef8926b22c1ce4d6e07ec2dc193f0810cff5851d7a2222c045 + languageName: node + linkType: hard + +"jest-watcher@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-watcher@npm:29.6.3" + dependencies: + "@jest/test-result": ^29.6.3 + "@jest/types": ^29.6.3 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + emittery: ^0.13.1 + jest-util: ^29.6.3 + string-length: ^4.0.1 + checksum: d31ab2076342d45959d5a7d9fdd88c0c5d52c2ea6fb3b1eabe7f8c28177d90355331beb4d844e171ed9e0341a2da901b7eefaa122505ba0f0ac88e58d29b3374 + languageName: node + linkType: hard + +"jest-worker@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-worker@npm:29.6.3" dependencies: - "@types/connect": ^3.4.33 - "@types/node": ^12.12.54 - "@types/ws": ^7.4.4 - JSONStream: ^1.3.5 - commander: ^2.20.3 - delay: ^5.0.0 - es6-promisify: ^5.0.0 - eyes: ^0.1.8 - isomorphic-ws: ^4.0.1 - json-stringify-safe: ^5.0.1 - uuid: ^8.3.2 - ws: ^7.4.5 + "@types/node": "*" + jest-util: ^29.6.3 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 8ffb24a2d4c70ed3032034a2601defccc19353d854d89459f58793c6c8f170f88038c6722073c8047c5734c8ec8d4902ebc955f4f7acb433c2499adf616388fc + languageName: node + linkType: hard + +"jest@npm:^29.6.3": + version: 29.6.3 + resolution: "jest@npm:29.6.3" + dependencies: + "@jest/core": ^29.6.3 + "@jest/types": ^29.6.3 + import-local: ^3.0.2 + jest-cli: ^29.6.3 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true bin: - jayson: bin/jayson.js - checksum: 86464322fbdc6db65d2bb4fc278cb6c86fad5c2a506065490d39459f09ba0d30f2b4fb740b33828a1424791419b6c8bd295dc54d361a4ad959bf70cc62b1ca7e + jest: bin/jest.js + checksum: dd4f53fb84f28b665b47c628222e5d3b624e9e0afa79b22afceef4f2a53dc0d8f0edd7ca254917ace5c94c3a7bf58c108563234c4fe34e86c679ce99633cfbe6 languageName: node linkType: hard @@ -7232,6 +8967,18 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^3.13.1": + version: 3.14.1 + resolution: "js-yaml@npm:3.14.1" + dependencies: + argparse: ^1.0.7 + esprima: ^4.0.0 + bin: + js-yaml: bin/js-yaml.js + checksum: bef146085f472d44dee30ec34e5cf36bf89164f5d585435a3d3da89e52622dff0b188a580e4ad091c3341889e14cb88cac6e4deb16dc5b1e9623bb0601fc255c + languageName: node + linkType: hard + "js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -7259,6 +9006,13 @@ __metadata: languageName: node linkType: hard +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 798ed4cf3354a2d9ccd78e86d2169515a0097a5c133337807cdf7f1fc32e1391d207ccfc276518cc1d7d8d4db93288b8a50ba4293d212ad1336e52a8ec0a941f + languageName: node + linkType: hard + "json-rpc-engine@npm:6.1.0, json-rpc-engine@npm:^6.1.0": version: 6.1.0 resolution: "json-rpc-engine@npm:6.1.0" @@ -7317,6 +9071,15 @@ __metadata: languageName: node linkType: hard +"json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 + languageName: node + linkType: hard + "jsonify@npm:^0.0.1": version: 0.0.1 resolution: "jsonify@npm:0.0.1" @@ -7379,6 +9142,13 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 + languageName: node + linkType: hard + "language-subtag-registry@npm:~0.3.2": version: 0.3.22 resolution: "language-subtag-registry@npm:0.3.22" @@ -7395,6 +9165,13 @@ __metadata: languageName: node linkType: hard +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 638401d534585261b6003db9d99afd244dfe82d75ddb6db5c0df412842d5ab30b2ef18de471aaec70fe69a46f17b4ae3c7f01d8a4e6580ef7adb9f4273ad1e55 + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -7541,6 +9318,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: ^3.0.2 + checksum: c154ae1cbb0c2206d1501a0e94df349653c92c8cbb25236d7e85190bcaf4567a03ac6eb43166fabfa36fd35623694da7233e88d9601fbf411a9a481d85dbd2cb + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -7557,6 +9343,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: ^7.5.3 + checksum: bf0731a2dd3aab4db6f3de1585cea0b746bb73eb5a02e3d8d72757e376e64e6ada190b1eddcde5b2f24a81b688a9897efd5018737d05e02e2a671dda9cff8a8a + languageName: node + linkType: hard + "make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" @@ -7588,6 +9383,15 @@ __metadata: languageName: node linkType: hard +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: 1.0.5 + checksum: b38a025a12c8146d6eeea5a7f2bf27d51d8ad6064da8ca9405fcf7bf9b54acd43e3b30ddd7abb9b1bfa4ddb266019133313482570ddb207de568f71ecfcf6060 + languageName: node + linkType: hard + "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -7617,6 +9421,13 @@ __metadata: languageName: node linkType: hard +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + "merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -7660,6 +9471,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + "minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -8020,6 +9838,20 @@ __metadata: languageName: node linkType: hard +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: d0b30b1ee6d961851c60d5eaa745d30b5c95d94bc0e74b81e5292f7c42a49e3af87f1eb9e89f59456f80645d679202537de751b7d72e9e40ceea40c5e449057e + languageName: node + linkType: hard + +"node-releases@npm:^2.0.13": + version: 2.0.13 + resolution: "node-releases@npm:2.0.13" + checksum: 17ec8f315dba62710cae71a8dad3cd0288ba943d2ece43504b3b1aa8625bf138637798ab470b1d9035b0545996f63000a8a926e0f6d35d0996424f8b6d36dda3 + languageName: node + linkType: hard + "node-releases@npm:^2.0.8": version: 2.0.10 resolution: "node-releases@npm:2.0.10" @@ -8052,6 +9884,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: ^3.0.0 + checksum: 5374c0cea4b0bbfdfae62da7bbdf1e1558d338335f4cacf2515c282ff358ff27b2ecb91ffa5330a8b14390ac66a1e146e10700440c1ab868208430f56b5f4d23 + languageName: node + linkType: hard + "npmlog@npm:^6.0.0": version: 6.0.2 resolution: "npmlog@npm:6.0.2" @@ -8180,6 +10021,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: 2478859ef817fc5d4e9c2f9e5728512ddd1dbc9fb7829ad263765bb6d3b91ce699d6e2332eef6b7dff183c2f490bd3349f1666427eaba4469fba0ac38dfd0d34 + languageName: node + linkType: hard + "open@npm:^8.4.0": version: 8.4.0 resolution: "open@npm:8.4.0" @@ -8230,7 +10080,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2": +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -8320,6 +10170,18 @@ __metadata: languageName: node linkType: hard +"parse-json@npm:^5.2.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": ^7.0.0 + error-ex: ^1.3.1 + json-parse-even-better-errors: ^2.3.0 + lines-and-columns: ^1.1.6 + checksum: 62085b17d64da57f40f6afc2ac1f4d95def18c4323577e1eced571db75d9ab59b297d1d10582920f84b15985cbfc6b6d450ccbf317644cfa176f3ed982ad87e2 + languageName: node + linkType: hard + "path-exists@npm:^3.0.0": version: 3.0.0 resolution: "path-exists@npm:3.0.0" @@ -8341,7 +10203,7 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^3.1.0": +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 @@ -8382,7 +10244,7 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf @@ -8455,6 +10317,22 @@ __metadata: languageName: node linkType: hard +"pirates@npm:^4.0.4": + version: 4.0.6 + resolution: "pirates@npm:4.0.6" + checksum: 46a65fefaf19c6f57460388a5af9ab81e3d7fd0e7bc44ca59d753cb5c4d0df97c6c6e583674869762101836d68675f027d60f841c105d72734df9dfca97cbcc6 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: ^4.0.0 + checksum: 9863e3f35132bf99ae1636d31ff1e1e3501251d480336edb1c211133c8d58906bed80f154a1d723652df1fda91e01c7442c2eeaf9dc83157c7ae89087e43c8d6 + languageName: node + linkType: hard + "pngjs@npm:^3.3.0": version: 3.4.0 resolution: "pngjs@npm:3.4.0" @@ -8598,6 +10476,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.6.3": + version: 29.6.3 + resolution: "pretty-format@npm:29.6.3" + dependencies: + "@jest/schemas": ^29.6.3 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 4e1c0db48e65571c22e80ff92123925ff8b3a2a89b71c3a1683cfde711004d492de32fe60c6bc10eea8bf6c678e5cbe544ac6c56cb8096e1eb7caf856928b1c4 + languageName: node + linkType: hard + "process-warning@npm:^1.0.0": version: 1.0.0 resolution: "process-warning@npm:1.0.0" @@ -8629,6 +10518,16 @@ __metadata: languageName: node linkType: hard +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: ^3.0.3 + sisteransi: ^1.0.5 + checksum: d8fd1fe63820be2412c13bfc5d0a01909acc1f0367e32396962e737cb2fc52d004f3302475d5ce7d18a1e8a79985f93ff04ee03007d091029c3f9104bffc007d + languageName: node + linkType: hard + "prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" @@ -8678,6 +10577,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^6.0.0": + version: 6.0.2 + resolution: "pure-rand@npm:6.0.2" + checksum: 79de33876a4f515d759c48e98d00756bbd916b4ea260cc572d7adfa4b62cace9952e89f0241d0410214554503d25061140fe325c66f845213d2b1728ba8d413e + languageName: node + linkType: hard + "qr.js@npm:0.0.0": version: 0.0.0 resolution: "qr.js@npm:0.0.0" @@ -8863,6 +10769,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.0": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" @@ -9088,6 +11001,15 @@ __metadata: languageName: node linkType: hard +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: ^5.0.0 + checksum: 546e0816012d65778e580ad62b29e975a642989108d9a3c5beabfb2304192fa3c9f9146fbdfe213563c6ff51975ae41bac1d3c6e047dd9572c94863a057b4d81 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -9095,6 +11017,20 @@ __metadata: languageName: node linkType: hard +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf + languageName: node + linkType: hard + +"resolve.exports@npm:^2.0.0": + version: 2.0.2 + resolution: "resolve.exports@npm:2.0.2" + checksum: 1c7778ca1b86a94f8ab4055d196c7d87d1874b96df4d7c3e67bbf793140f0717fd506dcafd62785b079cd6086b9264424ad634fb904409764c3509c3df1653f2 + languageName: node + linkType: hard + "resolve@npm:^1.1.7, resolve@npm:^1.20.0, resolve@npm:^1.22.0": version: 1.22.1 resolution: "resolve@npm:1.22.1" @@ -9355,6 +11291,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 + languageName: node + linkType: hard + "semver@npm:^7.3.5, semver@npm:^7.3.7": version: 7.3.7 resolution: "semver@npm:7.3.7" @@ -9377,6 +11322,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.3, semver@npm:^7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -9423,13 +11379,20 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 languageName: node linkType: hard +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: aba6438f46d2bfcef94cf112c835ab395172c75f67453fe05c340c770d3c402363018ae1ab4172a1026a90c47eaccf3af7b6ff6fa749a680c2929bd7fa2b37a4 + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -9510,6 +11473,16 @@ __metadata: languageName: node linkType: hard +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 933550047b6c1a2328599a21d8b7666507427c0f5ef5eaadd56b5da0fd9505e239053c66fe181bf1df469a3b7af9d775778eee283cbb7ae16b902ddc09e93a97 + languageName: node + linkType: hard + "source-map@npm:^0.5.0": version: 0.5.7 resolution: "source-map@npm:0.5.7" @@ -9517,6 +11490,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 + languageName: node + linkType: hard + "split-on-first@npm:^1.0.0": version: 1.1.0 resolution: "split-on-first@npm:1.1.0" @@ -9531,6 +11511,13 @@ __metadata: languageName: node linkType: hard +"sprintf-js@npm:~1.0.2": + version: 1.0.3 + resolution: "sprintf-js@npm:1.0.3" + checksum: 19d79aec211f09b99ec3099b5b2ae2f6e9cdefe50bc91ac4c69144b6d3928a640bb6ae5b3def70c2e85a2c3d9f5ec2719921e3a59d3ca3ef4b2fd1a4656a0df3 + languageName: node + linkType: hard + "ssri@npm:^9.0.0": version: 9.0.1 resolution: "ssri@npm:9.0.1" @@ -9540,6 +11527,15 @@ __metadata: languageName: node linkType: hard +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: ^2.0.0 + checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 + languageName: node + linkType: hard + "stream-browserify@npm:^3.0.0": version: 3.0.0 resolution: "stream-browserify@npm:3.0.0" @@ -9564,6 +11560,16 @@ __metadata: languageName: node linkType: hard +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: ^1.0.2 + strip-ansi: ^6.0.0 + checksum: ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505 + languageName: node + linkType: hard + "string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" @@ -9658,6 +11664,20 @@ __metadata: languageName: node linkType: hard +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 9dbcfbaf503c57c06af15fe2c8176fb1bf3af5ff65003851a102749f875a6dbe0ab3b30115eccf6e805e9d756830d3e40ec508b62b3f1ddf3761a20ebe29d3f3 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 69412b5e25731e1938184b5d489c32e340605bb611d6140344abc3421b7f3c6f9984b21dff296dfcf056681b82caa3bb4cc996a965ce37bcfad663e92eae9c64 + languageName: node + linkType: hard + "strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" @@ -9731,6 +11751,15 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: ^4.0.0 + checksum: c052193a7e43c6cdc741eb7f378df605636e01ad434badf7324f17fb60c69a880d8d8fcdcb562cf94c2350e57b937d7425ab5b8326c67c2adc48f7c87c1db406 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -9803,6 +11832,17 @@ __metadata: languageName: node linkType: hard +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": ^0.1.2 + glob: ^7.1.4 + minimatch: ^3.0.4 + checksum: 3b34a3d77165a2cb82b34014b3aba93b1c4637a5011807557dc2f3da826c59975a5ccad765721c4648b39817e3472789f9b0fa98fc854c5c1c7a1e632aacdc28 + languageName: node + linkType: hard + "text-encoding-utf-8@npm:^1.0.2": version: 1.0.2 resolution: "text-encoding-utf-8@npm:1.0.2" @@ -9868,6 +11908,13 @@ __metadata: languageName: node linkType: hard +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: cd922d9b853c00fe414c5a774817be65b058d54a2d01ebb415840960406c669a0fc632f66df885e24cb022ec812739199ccbdb8d1164c3e513f85bfca5ab2873 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -10003,6 +12050,13 @@ __metadata: languageName: node linkType: hard +"type-detect@npm:4.0.8": + version: 4.0.8 + resolution: "type-detect@npm:4.0.8" + checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 + languageName: node + linkType: hard + "type-fest@npm:^0.20.2": version: 0.20.2 resolution: "type-fest@npm:0.20.2" @@ -10010,6 +12064,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 + languageName: node + linkType: hard + "typedarray-to-buffer@npm:3.1.5": version: 3.1.5 resolution: "typedarray-to-buffer@npm:3.1.5" @@ -10119,6 +12180,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.0.11": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: b98327518f9a345c7cad5437afae4d2ae7d865f9779554baf2a200fdf4bac4969076b679b1115434bd6557376bdd37ca7583d0f9b8f8e302d7d4cc1e91b5f231 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -10228,6 +12303,17 @@ __metadata: languageName: node linkType: hard +"v8-to-istanbul@npm:^9.0.1": + version: 9.1.0 + resolution: "v8-to-istanbul@npm:9.1.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.12 + "@types/istanbul-lib-coverage": ^2.0.1 + convert-source-map: ^1.6.0 + checksum: 2069d59ee46cf8d83b4adfd8a5c1a90834caffa9f675e4360f1157ffc8578ef0f763c8f32d128334424159bb6b01f3876acd39cd13297b2769405a9da241f8d1 + languageName: node + linkType: hard + "valtio@npm:1.10.6": version: 1.10.6 resolution: "valtio@npm:1.10.6" @@ -10264,6 +12350,15 @@ __metadata: languageName: node linkType: hard +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: 1.0.12 + checksum: ad7a257ea1e662e57ef2e018f97b3c02a7240ad5093c392186ce0bcf1f1a60bbadd520d073b9beb921ed99f64f065efb63dfc8eec689a80e569f93c1c5d5e16c + languageName: node + linkType: hard + "warning@npm:^4.0.3": version: 4.0.3 resolution: "warning@npm:4.0.3" @@ -10383,6 +12478,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -10390,6 +12496,16 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^3.0.7 + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c + languageName: node + linkType: hard + "ws@npm:7.4.6": version: 7.4.6 resolution: "ws@npm:7.4.6" @@ -10471,6 +12587,20 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 48f7bb00dc19fc635a13a39fe547f527b10c9290e7b3e836b9a8f1ca04d4d342e85714416b3c2ab74949c9c66f9cebb0473e6bc353b79035356103b47641285d + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -10505,6 +12635,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + "yargs@npm:^13.2.4": version: 13.3.2 resolution: "yargs@npm:13.3.2" @@ -10542,6 +12679,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From 4d4dffe119ed20c346c1a54d03d1dc1102aff60e Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 22 Aug 2023 16:58:59 -0400 Subject: [PATCH 20/24] Port fixes for collateral balance checking --- .../tokens/adapters/AdapterFactory.ts | 9 +-- src/features/tokens/routes/fetch.test.ts | 29 ++++++--- src/features/tokens/routes/fetch.ts | 65 +++++++++---------- src/features/tokens/routes/types.ts | 6 +- src/features/tokens/types.ts | 2 +- src/features/transfer/useTokenTransfer.ts | 18 +++-- 6 files changed, 71 insertions(+), 58 deletions(-) diff --git a/src/features/tokens/adapters/AdapterFactory.ts b/src/features/tokens/adapters/AdapterFactory.ts index 7de66b60..d8d30ebc 100644 --- a/src/features/tokens/adapters/AdapterFactory.ts +++ b/src/features/tokens/adapters/AdapterFactory.ts @@ -103,20 +103,21 @@ export class AdapterFactory { } static HypTokenAdapterFromRouteDest(route: Route) { - const { type, destCaip2Id, destRouterAddress, baseTokenCaip19Id } = route; + const { type, destCaip2Id, destRouterAddress, destTokenCaip19Id, baseTokenCaip19Id } = route; + const tokenCaip19Id = destTokenCaip19Id || baseTokenCaip19Id; if (isRouteToCollateral(route)) { return AdapterFactory.selectHypAdapter( destCaip2Id, destRouterAddress, - baseTokenCaip19Id, + tokenCaip19Id, EvmHypCollateralAdapter, - isNativeToken(baseTokenCaip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, + isNativeToken(tokenCaip19Id) ? SealevelHypNativeAdapter : SealevelHypCollateralAdapter, ); } else if (isRouteToSynthetic(route)) { return AdapterFactory.selectHypAdapter( destCaip2Id, destRouterAddress, - baseTokenCaip19Id, + tokenCaip19Id, EvmHypSyntheticAdapter, SealevelHypSyntheticAdapter, ); diff --git a/src/features/tokens/routes/fetch.test.ts b/src/features/tokens/routes/fetch.test.ts index 1fabfb59..3b548f63 100644 --- a/src/features/tokens/routes/fetch.test.ts +++ b/src/features/tokens/routes/fetch.test.ts @@ -23,11 +23,13 @@ describe('computeTokenRoutes', () => { hypTokens: [ { decimals: 18, - tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + chain: 'ethereum:11155111', + router: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', }, { decimals: 18, - tokenCaip19Id: 'ethereum:44787/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + chain: 'ethereum:44787', + router: '0xEcbc0faAA269Cf649AC8950838664BB7B355BD6C', }, ], }, @@ -56,7 +58,7 @@ describe('computeTokenRoutes', () => { originRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', originDecimals: 18, destCaip2Id: 'ethereum:44787', - destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destRouterAddress: '0xEcbc0faAA269Cf649AC8950838664BB7B355BD6C', destDecimals: 18, }, ], @@ -84,7 +86,7 @@ describe('computeTokenRoutes', () => { originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', originDecimals: 18, destCaip2Id: 'ethereum:44787', - destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + destRouterAddress: '0xEcbc0faAA269Cf649AC8950838664BB7B355BD6C', destDecimals: 18, }, ], @@ -96,7 +98,7 @@ describe('computeTokenRoutes', () => { baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', originCaip2Id: 'ethereum:44787', - originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originRouterAddress: '0xEcbc0faAA269Cf649AC8950838664BB7B355BD6C', originDecimals: 18, destCaip2Id: 'ethereum:5', destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', @@ -109,7 +111,7 @@ describe('computeTokenRoutes', () => { baseTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', baseRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', originCaip2Id: 'ethereum:44787', - originRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + originRouterAddress: '0xEcbc0faAA269Cf649AC8950838664BB7B355BD6C', originDecimals: 18, destCaip2Id: 'ethereum:11155111', destRouterAddress: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', @@ -132,11 +134,13 @@ describe('computeTokenRoutes', () => { hypTokens: [ { decimals: 18, - tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + chain: 'ethereum:11155111', + router: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', }, { decimals: 6, - tokenCaip19Id: 'sealevel:1399811151/native:PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', + chain: 'sealevel:1399811151', + router: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', }, ], }, @@ -150,11 +154,13 @@ describe('computeTokenRoutes', () => { hypTokens: [ { decimals: 18, - tokenCaip19Id: 'ethereum:11155111/erc20:0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', + chain: 'ethereum:11155111', + router: '0xDcbc0faAA269Cf649AC8950838664BB7B355BD6B', }, { decimals: 18, - tokenCaip19Id: 'ethereum:5/erc20:0x145de8760021c4ac6676376691b78038d3DE9097', + chain: 'ethereum:5', + router: '0x145de8760021c4ac6676376691b78038d3DE9097', }, ], }, @@ -185,6 +191,8 @@ describe('computeTokenRoutes', () => { destCaip2Id: 'sealevel:1399811151', destRouterAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', destDecimals: 6, + destTokenCaip19Id: + 'sealevel:1399811151/native:00000000000000000000000000000000000000000000', }, ], }, @@ -230,6 +238,7 @@ describe('computeTokenRoutes', () => { destCaip2Id: 'ethereum:5', destRouterAddress: '0x145de8760021c4ac6676376691b78038d3DE9097', destDecimals: 18, + destTokenCaip19Id: 'ethereum:5/erc20:0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6', }, ], 'ethereum:11155111': [ diff --git a/src/features/tokens/routes/fetch.ts b/src/features/tokens/routes/fetch.ts index acb47501..d41a185c 100644 --- a/src/features/tokens/routes/fetch.ts +++ b/src/features/tokens/routes/fetch.ts @@ -3,13 +3,7 @@ import { ProtocolType } from '@hyperlane-xyz/sdk'; import { areAddressesEqual, bytesToProtocolAddress } from '../../../utils/addresses'; import { logger } from '../../../utils/logger'; import { getCaip2Id } from '../../caip/chains'; -import { - getCaip19Id, - getChainIdFromToken, - isNonFungibleToken, - parseCaip19Id, - resolveAssetNamespace, -} from '../../caip/tokens'; +import { getChainIdFromToken, isNonFungibleToken } from '../../caip/tokens'; import { getMultiProvider } from '../../multiProvider'; import { AdapterFactory } from '../adapters/AdapterFactory'; import { TokenMetadata, TokenMetadataWithHypTokens } from '../types'; @@ -38,24 +32,23 @@ export async function fetchRemoteHypTokens( remoteRouters.map(async (router) => { const destMetadata = multiProvider.getChainMetadata(router.domain); const protocol = destMetadata.protocol || ProtocolType.Ethereum; - const chainCaip2Id = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); - const namespace = resolveAssetNamespace(protocol, false, isNft, true); + const chain = getCaip2Id(protocol, multiProvider.getChainId(router.domain)); const formattedAddress = bytesToProtocolAddress(router.address, protocol); - const tokenCaip19Id = getCaip19Id(chainCaip2Id, namespace, formattedAddress); - if (isNft) return { tokenCaip19Id, decimals: 0 }; + if (isNft) return { chain, router: formattedAddress, decimals: 0 }; // Attempt to find the decimals from the token list const routerMetadata = allTokens.find((token) => areAddressesEqual(formattedAddress, token.routerAddress), ); - if (routerMetadata) return { tokenCaip19Id, decimals: routerMetadata.decimals }; + if (routerMetadata) + return { chain, router: formattedAddress, decimals: routerMetadata.decimals }; // Otherwise try to query the contract const remoteAdapter = AdapterFactory.HypSyntheticTokenAdapterFromAddress( baseTokenCaip19Id, - chainCaip2Id, + chain, formattedAddress, ); const metadata = await remoteAdapter.getMetadata(); - return { tokenCaip19Id, decimals: metadata.decimals }; + return { chain, router: formattedAddress, decimals: metadata.decimals }; }), ); return { ...baseToken, hypTokens }; @@ -84,37 +77,39 @@ export function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { decimals: baseDecimals, } = token; const baseChainCaip2Id = getChainIdFromToken(baseTokenCaip19Id); - const { chainCaip2Id: remoteCaip2Id, address: remoteRouterAddress } = parseCaip19Id( - remoteHypToken.tokenCaip19Id, - ); - const remoteDecimals = remoteHypToken.decimals; + const { + chain: remoteChainCaip2Id, + router: remoteRouterAddress, + decimals: remoteDecimals, + } = remoteHypToken; // Check if the token list contains the dest router address, meaning it's also a base collateral token - const isRemoteCollateral = tokensHasRouter(tokens, remoteRouterAddress); + const remoteBaseTokenConfig = findTokenByRouter(tokens, remoteRouterAddress); const commonRouteProps = { baseTokenCaip19Id, baseRouterAddress }; // Register a route from the base to the remote - tokenRoutes[baseChainCaip2Id][remoteCaip2Id]?.push({ - type: isRemoteCollateral + tokenRoutes[baseChainCaip2Id][remoteChainCaip2Id]?.push({ + type: remoteBaseTokenConfig ? RouteType.CollateralToCollateral : RouteType.CollateralToSynthetic, ...commonRouteProps, originCaip2Id: baseChainCaip2Id, originRouterAddress: baseRouterAddress, originDecimals: baseDecimals, - destCaip2Id: remoteCaip2Id, + destCaip2Id: remoteChainCaip2Id, destRouterAddress: remoteRouterAddress, destDecimals: remoteDecimals, + destTokenCaip19Id: remoteBaseTokenConfig ? remoteBaseTokenConfig.tokenCaip19Id : undefined, }); // If the remote is not a synthetic (i.e. it's a native/collateral token with it's own config) // then stop here to avoid duplicate route entries. - if (isRemoteCollateral) continue; + if (remoteBaseTokenConfig) continue; // Register a route back from the synthetic remote to the base - tokenRoutes[remoteCaip2Id][baseChainCaip2Id]?.push({ + tokenRoutes[remoteChainCaip2Id][baseChainCaip2Id]?.push({ type: RouteType.SyntheticToCollateral, ...commonRouteProps, - originCaip2Id: remoteCaip2Id, + originCaip2Id: remoteChainCaip2Id, originRouterAddress: remoteRouterAddress, originDecimals: remoteDecimals, destCaip2Id: baseChainCaip2Id, @@ -126,18 +121,16 @@ export function computeTokenRoutes(tokens: TokenMetadataWithHypTokens[]) { // This assumes the synthetics were all enrolled to connect to each other // which is the deployer's default behavior for (const otherHypToken of token.hypTokens) { - const { chainCaip2Id: otherSynCaip2Id, address: otherHypTokenAddress } = parseCaip19Id( - otherHypToken.tokenCaip19Id, - ); + const { chain: otherSynCaip2Id, router: otherHypTokenAddress } = otherHypToken; // Skip if it's same hypToken as parent loop (no route to self) - // or if if remote isn't a synthetic - if (otherHypToken === remoteHypToken || tokensHasRouter(tokens, otherHypTokenAddress)) - continue; + if (otherHypToken === remoteHypToken) continue; + // Also skip if remote isn't a synthetic (i.e. has a collateral/native config) + if (findTokenByRouter(tokens, otherHypTokenAddress)) continue; - tokenRoutes[remoteCaip2Id][otherSynCaip2Id]?.push({ + tokenRoutes[remoteChainCaip2Id][otherSynCaip2Id]?.push({ type: RouteType.SyntheticToSynthetic, ...commonRouteProps, - originCaip2Id: remoteCaip2Id, + originCaip2Id: remoteChainCaip2Id, originRouterAddress: remoteRouterAddress, originDecimals: remoteDecimals, destCaip2Id: otherSynCaip2Id, @@ -155,12 +148,12 @@ function getChainsFromTokens(tokens: TokenMetadataWithHypTokens[]): ChainCaip2Id for (const token of tokens) { chains.add(getChainIdFromToken(token.tokenCaip19Id)); for (const hypToken of token.hypTokens) { - chains.add(getChainIdFromToken(hypToken.tokenCaip19Id)); + chains.add(hypToken.chain); } } return Array.from(chains); } -function tokensHasRouter(tokens: TokenMetadataWithHypTokens[], router: Address) { - return !!tokens.find((t) => areAddressesEqual(t.routerAddress, router)); +function findTokenByRouter(tokens: TokenMetadataWithHypTokens[], router: Address) { + return tokens.find((t) => areAddressesEqual(t.routerAddress, router)); } diff --git a/src/features/tokens/routes/types.ts b/src/features/tokens/routes/types.ts index 31772af0..5939c3fc 100644 --- a/src/features/tokens/routes/types.ts +++ b/src/features/tokens/routes/types.ts @@ -7,7 +7,8 @@ export enum RouteType { export interface Route { type: RouteType; - baseTokenCaip19Id: TokenCaip19Id; // i.e. the underlying 'collateralized' token + // The underlying 'collateralized' token: + baseTokenCaip19Id: TokenCaip19Id; baseRouterAddress: Address; originCaip2Id: ChainCaip2Id; originRouterAddress: Address; @@ -15,6 +16,9 @@ export interface Route { destCaip2Id: ChainCaip2Id; destRouterAddress: Address; destDecimals: number; + // The underlying token on the destination chain + // Only set for CollateralToCollateral routes (b.c. sealevel need it) + destTokenCaip19Id?: TokenCaip19Id; } export type RoutesMap = Record>; diff --git a/src/features/tokens/types.ts b/src/features/tokens/types.ts index 92ca2ad1..2f816ec8 100644 --- a/src/features/tokens/types.ts +++ b/src/features/tokens/types.ts @@ -88,7 +88,7 @@ export type TokenMetadata = CollateralTokenMetadata | NativeTokenMetadata; * Extended types including synthetic hyp token addresses */ interface HypTokens { - hypTokens: Array<{ tokenCaip19Id: TokenCaip19Id; decimals: number }>; + hypTokens: Array<{ chain: ChainCaip2Id; router: Address; decimals: number }>; } type NativeTokenMetadataWithHypTokens = NativeTokenMetadata & HypTokens; diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 1c663b7e..cee5683b 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'; import { HyperlaneCore, ProtocolType } from '@hyperlane-xyz/sdk'; import { toastTxSuccess } from '../../components/toast/TxSuccessToast'; -import { toWei } from '../../utils/amount'; +import { convertDecimals, toWei } from '../../utils/amount'; import { logger } from '../../utils/logger'; import { getProtocolType, parseCaip2Id } from '../caip/chains'; import { isNativeToken, isNonFungibleToken } from '../caip/tokens'; @@ -184,11 +184,17 @@ async function executeTransfer({ // cover the remote transfer. This ensures the balance is sufficient or throws. async function ensureSufficientCollateral(route: Route, weiAmount: string, isNft?: boolean) { if (!isRouteToCollateral(route) || isNft) return; - const adapter = AdapterFactory.TokenAdapterFromAddress(route.baseTokenCaip19Id); - logger.debug('Checking collateral balance for token', route.baseTokenCaip19Id); - const balance = await adapter.getBalance(route.baseRouterAddress); - if (BigNumber.from(balance).lt(weiAmount)) { - throw new Error('Collateral contract has insufficient balance'); + logger.debug('Ensuring collateral balance for route', route); + const adapter = AdapterFactory.HypTokenAdapterFromRouteDest(route); + const destinationBalance = await adapter.getBalance(route.destRouterAddress); + const destinationBalanceInOriginDecimals = convertDecimals( + route.destDecimals, + route.originDecimals, + destinationBalance, + ); + if (destinationBalanceInOriginDecimals.lt(weiAmount)) { + toast.error('Collateral contract balance insufficient for transfer'); + throw new Error('Insufficient collateral balance'); } } From c2bd6da86395125af43abf72657d92ef895ec472 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 22 Aug 2023 17:06:52 -0400 Subject: [PATCH 21/24] Remove balance and route hacks --- src/features/tokens/balances.tsx | 23 ++++------------------- src/features/tokens/routes/utils.ts | 7 +------ 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/features/tokens/balances.tsx b/src/features/tokens/balances.tsx index d756160d..fe205a47 100644 --- a/src/features/tokens/balances.tsx +++ b/src/features/tokens/balances.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import { areAddressesEqual, isValidAddress } from '../../utils/addresses'; import { logger } from '../../utils/logger'; import { getProtocolType } from '../caip/chains'; -import { getChainIdFromToken, parseCaip19Id, tryGetChainIdFromToken } from '../caip/tokens'; +import { parseCaip19Id, tryGetChainIdFromToken } from '../caip/tokens'; import { getProvider } from '../multiProvider'; import { useStore } from '../store'; import { TransferFormValues } from '../transfer/types'; @@ -71,27 +71,12 @@ export function useDestinationBalance( tokenRoutes, ], queryFn: async () => { - // NOTE: this is a hack to accommodate destination balances, specifically the case - // when the destination is a Sealevel chain and is a non-synthetic warp route. - // This only really works with the specific setup of tokens.ts. - - // This searches for the route where the origin chain is destinationCaip2Id - // and the destination chain is originCaip2Id and where the origin is a base token. - const targetBaseCaip19Id = tokenRoutes[destinationCaip2Id][originCaip2Id].find( - (r) => getChainIdFromToken(r.baseTokenCaip19Id) === destinationCaip2Id, - )!.baseTokenCaip19Id; - const route = getTokenRoute( - destinationCaip2Id, - originCaip2Id, - targetBaseCaip19Id, - tokenRoutes, - ); + const route = getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); const protocol = getProtocolType(destinationCaip2Id); if (!route || !recipientAddress || !isValidAddress(recipientAddress, protocol)) return null; - - const adapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(route); + const adapter = AdapterFactory.HypTokenAdapterFromRouteDest(route); const balance = await adapter.getBalance(recipientAddress); - return { balance, decimals: route.originDecimals }; + return { balance, decimals: route.destDecimals }; }, refetchInterval: 5000, }); diff --git a/src/features/tokens/routes/utils.ts b/src/features/tokens/routes/utils.ts index 6e130b25..81e6774f 100644 --- a/src/features/tokens/routes/utils.ts +++ b/src/features/tokens/routes/utils.ts @@ -1,5 +1,3 @@ -import { getChainIdFromToken } from '../../caip/tokens'; - import { Route, RouteType, RoutesMap } from './types'; export function getTokenRoutes( @@ -28,10 +26,7 @@ export function hasTokenRoute( tokenCaip19Id: TokenCaip19Id, tokenRoutes: RoutesMap, ): boolean { - const tokenRoute = getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); - // This will break things if there are other warp routes configured! - // This only looks for routes in which the origin is the base token. - return !!tokenRoute && getChainIdFromToken(tokenCaip19Id) === originCaip2Id; + return !!getTokenRoute(originCaip2Id, destinationCaip2Id, tokenCaip19Id, tokenRoutes); } export function isRouteToCollateral(route: Route) { From c9e2e1b0788e2ad2687516150480f9fe98646387 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 22 Aug 2023 17:07:29 -0400 Subject: [PATCH 22/24] Remove dead const --- src/features/transfer/useTokenTransfer.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index 1db2ae4f..d59d723f 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -27,8 +27,6 @@ import { import { TransferContext, TransferFormValues, TransferStatus } from './types'; -const COLLATERAL_CONTRACT_BALANCE_INSUFFICIENT_ERROR = 'Collateral contract balance insufficient'; - export function useTokenTransfer(onDone?: () => void) { const { transfers, addTransfer, updateTransferStatus } = useStore((s) => ({ transfers: s.transfers, From e791e29f08d176fefa6e69ba2d3046b3d62ebf76 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Tue, 22 Aug 2023 17:16:04 -0400 Subject: [PATCH 23/24] Fix build error from merge conflict --- src/features/transfer/useTokenTransfer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/transfer/useTokenTransfer.ts b/src/features/transfer/useTokenTransfer.ts index d59d723f..cee5683b 100644 --- a/src/features/transfer/useTokenTransfer.ts +++ b/src/features/transfer/useTokenTransfer.ts @@ -126,7 +126,7 @@ async function executeTransfer({ params: values, }); - await ensureSufficientCollateral(tokenRoutes, tokenRoute, weiAmountOrId, isNft); + await ensureSufficientCollateral(tokenRoute, weiAmountOrId, isNft); const hypTokenAdapter = AdapterFactory.HypTokenAdapterFromRouteOrigin(tokenRoute); From 0235fc025c1dbbd3d8b2b78d72a4cf863469b810 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 25 Aug 2023 13:22:48 -0400 Subject: [PATCH 24/24] Update zebec token name and logo --- src/consts/tokens.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/consts/tokens.ts b/src/consts/tokens.ts index 4fa734f6..eaa3a357 100644 --- a/src/consts/tokens.ts +++ b/src/consts/tokens.ts @@ -7,9 +7,10 @@ export const tokenList: WarpTokenConfig = [ chainId: 97, address: '0x64544969ed7ebf5f083679233325356ebe738930', hypCollateralAddress: '0x31b5234A896FbC4b3e2F7237592D054716762131', - symbol: 'ZBC', + symbol: 'wZBC', name: 'Zebec', decimals: 18, + logoURI: '/logos/zebec.png', }, // proteustestnet @@ -17,9 +18,10 @@ export const tokenList: WarpTokenConfig = [ type: 'native', chainId: 88002, hypNativeAddress: '0x34A9af13c5555BAD0783C220911b9ef59CfDBCEf', - symbol: 'ZBC', + symbol: 'wZBC', name: 'Zebec', decimals: 18, + logoURI: '/logos/zebec.png', }, // solanadevnet @@ -29,8 +31,9 @@ export const tokenList: WarpTokenConfig = [ address: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', hypCollateralAddress: 'PJH5QAbxAqrrnSXfH3GHR8icua8CDFZmo97z91xmpvx', name: 'Zebec', - symbol: 'ZBC', + symbol: 'wZBC', decimals: 6, isSpl2022: false, + logoURI: '/logos/zebec.png', }, ];