From b9fc02910f533c376b550d25e05284ebaf8c9830 Mon Sep 17 00:00:00 2001 From: Jon Ator Date: Mon, 3 Jun 2024 15:46:41 -0500 Subject: [PATCH 1/5] add ibc provider tests --- packages/bridge/src/errors.ts | 1 + .../ibc/__tests__/ibc-bridge-provider.spec.ts | 235 ++++++++++++++++++ packages/bridge/src/ibc/index.ts | 9 + 3 files changed, 245 insertions(+) create mode 100644 packages/bridge/src/ibc/__tests__/ibc-bridge-provider.spec.ts diff --git a/packages/bridge/src/errors.ts b/packages/bridge/src/errors.ts index 6054d1237e..def193473c 100644 --- a/packages/bridge/src/errors.ts +++ b/packages/bridge/src/errors.ts @@ -51,4 +51,5 @@ export enum BridgeError { CreateEVMTxError = "CreateEVMTxError", NoQuotesError = "NoQuotesError", UnsupportedQuoteError = "UnsupportedQuoteError", + InsufficientAmount = "InsufficientAmountError", } diff --git a/packages/bridge/src/ibc/__tests__/ibc-bridge-provider.spec.ts b/packages/bridge/src/ibc/__tests__/ibc-bridge-provider.spec.ts new file mode 100644 index 0000000000..1608aa1441 --- /dev/null +++ b/packages/bridge/src/ibc/__tests__/ibc-bridge-provider.spec.ts @@ -0,0 +1,235 @@ +import { Int } from "@keplr-wallet/unit"; +import { estimateGasFee } from "@osmosis-labs/tx"; +import { CacheEntry } from "cachified"; +import { LRUCache } from "lru-cache"; + +import { MockAssetLists } from "../../__tests__/mock-asset-lists"; +import { MockChains } from "../../__tests__/mock-chains"; +import { BridgeQuoteError } from "../../errors"; +import { + BridgeProviderContext, + BridgeQuote, + GetBridgeQuoteParams, +} from "../../interface"; +import { IbcBridgeProvider } from "../index"; + +jest.mock("@osmosis-labs/tx", () => ({ + estimateGasFee: jest.fn(), +})); + +const mockContext: BridgeProviderContext = { + chainList: MockChains, + assetLists: MockAssetLists, + getTimeoutHeight: jest.fn().mockResolvedValue("1000-1000"), + env: "mainnet", + cache: new LRUCache({ max: 10 }), +}; + +// deposit of ATOM from Cosmos Hub to Osmosis +const mockAtomToOsmosis: GetBridgeQuoteParams = { + fromChain: { + chainId: "cosmoshub-4", + chainType: "cosmos", + }, + toChain: { + chainId: "osmosis-1", + chainType: "cosmos", + }, + fromAsset: { + address: "uatom", + sourceDenom: "uatom", + denom: "ATOM", + decimals: 6, + }, + toAsset: { + address: + "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", + sourceDenom: "uatom", + denom: "ATOM", + decimals: 6, + }, + fromAmount: "1000000", // 1 ATOM + fromAddress: "osmo1...", + toAddress: "cosmos1...", +}; + +const mockAtomFromOsmosis: GetBridgeQuoteParams = { + fromChain: { + chainId: "osmosis-1", + chainType: "cosmos", + }, + toChain: { + chainId: "cosmoshub-4", + chainType: "cosmos", + }, + fromAsset: { + address: + "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", + sourceDenom: "uatom", + denom: "ATOM", + decimals: 6, + }, + toAsset: { + address: "uatom", + sourceDenom: "uatom", + denom: "ATOM", + decimals: 6, + }, + fromAmount: "1000000", // 1 ATOM + fromAddress: "osmo1...", + toAddress: "cosmos1...", +}; + +describe("IbcBridgeProvider", () => { + let provider: IbcBridgeProvider; + + beforeEach(() => { + provider = new IbcBridgeProvider(mockContext); + jest.clearAllMocks(); + }); + + it("should throw an error if fromChain or toChain is not cosmos", async () => { + const invalidParams: GetBridgeQuoteParams = { + ...mockAtomToOsmosis, + fromChain: { chainId: 1, chainType: "evm" }, + }; + await expect(provider.getQuote(invalidParams)).rejects.toThrow( + BridgeQuoteError + ); + }); + + it("should throw an error if gas cost exceeds transfer amount", async () => { + (estimateGasFee as jest.Mock).mockResolvedValue({ + amount: [ + { + amount: new Int(mockAtomFromOsmosis.fromAmount) + .add(new Int(100)) + .toString(), + denom: "uatom", + isNeededForTx: true, + }, + ], + }); + + await expect(provider.getQuote(mockAtomToOsmosis)).rejects.toThrow( + BridgeQuoteError + ); + }); + + it("should return a valid BridgeQuote", async () => { + (estimateGasFee as jest.Mock).mockResolvedValue({ + amount: [{ amount: "5000", denom: "uatom", isNeededForTx: true }], + }); + + const quote: BridgeQuote = await provider.getQuote(mockAtomToOsmosis); + + expect(quote).toHaveProperty("input"); + expect(quote).toHaveProperty("expectedOutput"); + expect(quote).toHaveProperty("fromChain"); + expect(quote.fromChain.chainId).toBe(mockAtomToOsmosis.fromChain.chainId); + expect(quote).toHaveProperty("toChain"); + expect(quote.toChain.chainId).toBe(mockAtomToOsmosis.toChain.chainId); + expect(quote).toHaveProperty("transferFee"); + expect(quote.transferFee.amount).toBe("0"); + expect(quote).toHaveProperty("estimatedTime"); + expect(quote).toHaveProperty("estimatedGasFee"); + expect(quote).toHaveProperty("estimatedGasFee"); + expect(quote.estimatedGasFee!.amount).toBe("5000"); + expect(quote).toHaveProperty("transactionRequest"); + }); + + it("should calculate the correct toAmount when gas fee is needed for tx", async () => { + (estimateGasFee as jest.Mock).mockResolvedValue({ + amount: [{ amount: "5000", denom: "uatom", isNeededForTx: true }], + }); + + const quote: BridgeQuote = await provider.getQuote(mockAtomToOsmosis); + + expect(quote.expectedOutput.amount).toBe( + new Int(mockAtomToOsmosis.fromAmount).sub(new Int("5000")).toString() + ); + }); + + it("should calculate the correct toAmount when gas fee is not needed for tx", async () => { + (estimateGasFee as jest.Mock).mockResolvedValue({ + amount: [{ amount: "5000", denom: "uatom", isNeededForTx: false }], + }); + + const quote: BridgeQuote = await provider.getQuote(mockAtomToOsmosis); + + expect(quote.expectedOutput.amount).toBe(mockAtomToOsmosis.fromAmount); + }); + + describe("getIbcSource", () => { + // extend class to access protected method + class ExtendedIbcBridgeProvider extends IbcBridgeProvider { + public getIbcSourcePublic(params: GetBridgeQuoteParams): { + sourceChannel: string; + sourcePort: string; + sourceDenom: string; + } { + return this.getIbcSource(params); + } + } + + let provider: ExtendedIbcBridgeProvider; + + beforeEach(() => { + provider = new ExtendedIbcBridgeProvider(mockContext); + }); + + it("should return the correct channel, port and denom for transfer from source", () => { + const result = provider.getIbcSourcePublic(mockAtomFromOsmosis); + + expect(result.sourceChannel).toBe("channel-0"); + expect(result.sourcePort).toBe("transfer"); + expect(result.sourceDenom).toBe( + "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" + ); + }); + + it("should return the correct channel, port and denom for transfer from counterparty", () => { + const result = provider.getIbcSourcePublic(mockAtomToOsmosis); + + expect(result.sourceChannel).toBe("channel-141"); + expect(result.sourcePort).toBe("transfer"); + expect(result.sourceDenom).toBe("uatom"); + }); + + it("should throw if asset not found", () => { + const invalidParams: GetBridgeQuoteParams = { + ...mockAtomToOsmosis, + toAsset: { + ...mockAtomToOsmosis.toAsset, + sourceDenom: "not-found", + }, + fromAsset: { + ...mockAtomToOsmosis.fromAsset, + sourceDenom: "not-found", + }, + }; + + expect(() => provider.getIbcSourcePublic(invalidParams)).toThrow( + BridgeQuoteError + ); + }); + + it("should throw if there's no transfer method", () => { + const invalidParams: GetBridgeQuoteParams = { + ...mockAtomToOsmosis, + toAsset: { + ...mockAtomToOsmosis.toAsset, + sourceDenom: "uosmo", + }, + fromAsset: { + ...mockAtomToOsmosis.fromAsset, + sourceDenom: "uosmo", + }, + }; + + expect(() => provider.getIbcSourcePublic(invalidParams)).toThrow( + BridgeQuoteError + ); + }); + }); +}); diff --git a/packages/bridge/src/ibc/index.ts b/packages/bridge/src/ibc/index.ts index d38917e043..b586362403 100644 --- a/packages/bridge/src/ibc/index.ts +++ b/packages/bridge/src/ibc/index.ts @@ -66,6 +66,15 @@ export class IbcBridgeProvider implements BridgeProvider { ? new Int(params.fromAmount).sub(new Int(gasFee.amount)).toString() : params.fromAmount; + if (new Int(toAmount).lte(new Int(0))) { + throw new BridgeQuoteError([ + { + errorType: BridgeError.InsufficientAmount, + message: "Insufficient amount for fees", + }, + ]); + } + return { input: { amount: params.fromAmount, From 10725b20d799a38632ef64c992498dc237d032fa Mon Sep 17 00:00:00 2001 From: Jon Ator Date: Tue, 4 Jun 2024 08:46:07 -0500 Subject: [PATCH 2/5] add poll status tests --- packages/tx/src/__tests__/poll-status.spec.ts | 316 ++++++++++++++++++ packages/tx/src/poll-status.ts | 17 +- 2 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 packages/tx/src/__tests__/poll-status.spec.ts diff --git a/packages/tx/src/__tests__/poll-status.spec.ts b/packages/tx/src/__tests__/poll-status.spec.ts new file mode 100644 index 0000000000..d8d6c63a94 --- /dev/null +++ b/packages/tx/src/__tests__/poll-status.spec.ts @@ -0,0 +1,316 @@ +import { queryRPCStatus, QueryStatusResponse } from "@osmosis-labs/server"; + +import { PollingStatusSubscription, StatusHandler } from "../index"; + +jest.useFakeTimers(); + +/** Stuff that's not used but in the status response type. */ +const baseMockStatusResult: QueryStatusResponse["result"] = { + validator_info: { + address: "mock_address", + pub_key: { + type: "mock_type", + value: "mock_value", + }, + voting_power: "mock_voting_power", + }, + node_info: { + protocol_version: { + p2p: "mock_p2p", + block: "mock_block", + app: "mock_app", + }, + id: "mock_id", + listen_addr: "mock_listen_addr", + network: "mock_network", + version: "mock_version", + channels: "mock_channels", + moniker: "mock_moniker", + other: { + tx_index: "on" as const, + rpc_address: "mock_rpc_address", + }, + }, + sync_info: { + // overwrite these, but is otherwise a reasonable time range + latest_block_hash: "mock_latest_block_hash", + latest_app_hash: "mock_latest_app_hash", + earliest_block_hash: "mock_earliest_block_hash", + earliest_app_hash: "mock_earliest_app_hash", + latest_block_height: "100", + earliest_block_height: "90", + latest_block_time: new Date(Date.now() - 10000).toISOString(), + earliest_block_time: new Date(Date.now() - 20000).toISOString(), + catching_up: false, + }, +}; + +jest.mock("@osmosis-labs/server", () => ({ + queryRPCStatus: jest.fn().mockResolvedValue({ + jsonrpc: "2.0", + id: 1, + result: { + validator_info: { + address: "mock_address", + pub_key: { + type: "mock_type", + value: "mock_value", + }, + voting_power: "mock_voting_power", + }, + node_info: { + protocol_version: { + p2p: "mock_p2p", + block: "mock_block", + app: "mock_app", + }, + id: "mock_id", + listen_addr: "mock_listen_addr", + network: "mock_network", + version: "mock_version", + channels: "mock_channels", + moniker: "mock_moniker", + other: { + tx_index: "on" as const, + rpc_address: "mock_rpc_address", + }, + }, + sync_info: { + // reasonable time range + latest_block_hash: "mock_latest_block_hash", + latest_app_hash: "mock_latest_app_hash", + earliest_block_hash: "mock_earliest_block_hash", + earliest_app_hash: "mock_earliest_app_hash", + latest_block_height: "100", + earliest_block_height: "90", + latest_block_time: new Date(Date.now() - 10000).toISOString(), + earliest_block_time: new Date(Date.now() - 20000).toISOString(), + catching_up: false, + }, + }, + }), + DEFAULT_LRU_OPTIONS: { max: 10 }, +})); + +describe("PollingStatusSubscription", () => { + const mockRPC = "http://mock-rpc-url"; + const defaultBlockTimeMs = 7500; + let subscription: PollingStatusSubscription; + + beforeEach(() => { + subscription = new PollingStatusSubscription(mockRPC, defaultBlockTimeMs); + jest.clearAllMocks(); + }); + + it("should initialize with zero subscriptions", () => { + expect(subscription.subscriptionCount).toBe(0); + }); + + it("should handle errors in startSubscription gracefully", async () => { + const handler: StatusHandler = jest.fn(); + (queryRPCStatus as jest.Mock).mockRejectedValue(new Error("Network error")); + + // starts loop + subscription.subscribe(handler); + + // end loop and flush event loop + jest.runAllTimers(); + + expect(handler).not.toHaveBeenCalled(); + }); + + it("should increase subscription count when a handler is subscribed", () => { + const handler: StatusHandler = jest.fn(); + subscription.subscribe(handler); + + expect(subscription.subscriptionCount).toBe(1); + + // end loop and flush event loop + jest.runAllTimers(); + }); + + it("should decrease subscription count when a handler is unsubscribed", () => { + const handler: StatusHandler = jest.fn(); + const unsubscribe = subscription.subscribe(handler); + unsubscribe(); + + jest.runAllTimers(); + + expect(subscription.subscriptionCount).toBe(0); + }); + + it("should call handlers with status and block time", async () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "100", + earliest_block_height: "90", + latest_block_time: new Date().toISOString(), + earliest_block_time: new Date(Date.now() - 10000).toISOString(), + }, + }, + }; + + (queryRPCStatus as jest.Mock).mockResolvedValue(mockStatus); + + const handler: StatusHandler = jest.fn(); + subscription.subscribe(handler); + + // Run all timers to ensure the subscription logic completes + jest.runAllTimers(); + + // Ensure all promises are resolved by pushing to the event queue + await Promise.resolve(); + + expect(handler).toHaveBeenCalledWith(mockStatus, expect.any(Number)); + }); + + describe("calcAverageBlockTimeMs", () => { + class TestPollingStatusSubscription extends PollingStatusSubscription { + public test(status: QueryStatusResponse): number { + return this.calcAverageBlockTimeMs(status); + } + } + + let avgBlockTimeSub: TestPollingStatusSubscription; + + beforeEach(() => { + avgBlockTimeSub = new TestPollingStatusSubscription( + mockRPC, + defaultBlockTimeMs + ); + }); + + it("should return default block time if catching up", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: true, + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + expect(blockTime).toBe(defaultBlockTimeMs); + }); + + it("should return default block time if block height is NaN", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "NaN", + earliest_block_height: "NaN", + latest_block_time: new Date().toISOString(), + earliest_block_time: new Date().toISOString(), + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + expect(blockTime).toBe(defaultBlockTimeMs); + }); + + it("should calculate a reasonable avg default block time", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "100", + earliest_block_height: "90", + latest_block_time: new Date(Date.now() - 10000).toISOString(), + earliest_block_time: new Date(Date.now() - 20000).toISOString(), + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + const expectedBlockTime = + (new Date(mockStatus.result.sync_info.latest_block_time).getTime() - + new Date(mockStatus.result.sync_info.earliest_block_time).getTime()) / + (parseInt(mockStatus.result.sync_info.latest_block_height) - + parseInt(mockStatus.result.sync_info.earliest_block_height)); + expect(blockTime).toBe(Math.ceil(expectedBlockTime)); + }); + + it("should return default block time if block time is unreasonable", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "100", + earliest_block_height: "90", + latest_block_time: new Date().toISOString(), + earliest_block_time: new Date(Date.now() - 1000000).toISOString(), + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + expect(blockTime).toBe(defaultBlockTimeMs); + }); + + it("should return default block time if latest block height is less or equal to than earliest block height", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "80", + earliest_block_height: "90", + latest_block_time: new Date().toISOString(), + earliest_block_time: new Date(Date.now() - 1000000).toISOString(), + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + expect(blockTime).toBe(defaultBlockTimeMs); + }); + + it("should return default block time if an invalid block time value is returned", () => { + const mockStatus: QueryStatusResponse = { + jsonrpc: "2.0", + id: 1, + result: { + ...baseMockStatusResult, + sync_info: { + ...baseMockStatusResult.sync_info, + catching_up: false, + latest_block_height: "80", + earliest_block_height: "90", + latest_block_time: "invalid", + earliest_block_time: new Date(Date.now() - 1000000).toISOString(), + }, + }, + }; + + const blockTime = avgBlockTimeSub.test(mockStatus); + expect(blockTime).toBe(defaultBlockTimeMs); + }); + }); +}); diff --git a/packages/tx/src/poll-status.ts b/packages/tx/src/poll-status.ts index c87f41cbd3..36353cf722 100644 --- a/packages/tx/src/poll-status.ts +++ b/packages/tx/src/poll-status.ts @@ -36,18 +36,20 @@ export class PollingStatusSubscription { } protected async startSubscription() { + let timeoutId: NodeJS.Timeout | undefined; while (this._subscriptionCount > 0) { try { const status = await queryRPCStatus({ restUrl: this.rpc }); const blockTime = this.calcAverageBlockTimeMs(status); this._handlers.forEach((handler) => handler(status, blockTime)); await new Promise((resolve) => { - setTimeout(resolve, blockTime); + timeoutId = setTimeout(resolve, blockTime); }); } catch (e: any) { - console.error(`Failed to fetch /status: ${e?.toString()}`); + console.error(`Failed to fetch /status: ${e}`); } } + if (timeoutId) clearTimeout(timeoutId); } protected increaseSubscriptionCount() { @@ -68,6 +70,8 @@ export class PollingStatusSubscription { * The estimate is a rough estimate from the latest and earliest block times in sync info, so it may * not be fully up to date if block time changes. * + * Prefers returning defaults vs throwing errors. + * * Returns the default block time if the calculated block time is unexpected or unreasonable. */ protected calcAverageBlockTimeMs(status: QueryStatusResponse): number { @@ -87,6 +91,11 @@ export class PollingStatusSubscription { return this.defaultBlockTimeMs; } + // prevent division by zero + if (latestBlockHeight <= earliestBlockHeight) { + return this.defaultBlockTimeMs; + } + const latestBlockTime = new Date( status.result.sync_info.latest_block_time ).getTime(); @@ -94,10 +103,6 @@ export class PollingStatusSubscription { status.result.sync_info.earliest_block_time ).getTime(); - if (latestBlockHeight <= earliestBlockHeight) { - return this.defaultBlockTimeMs; - } - const avg = Math.ceil( (latestBlockTime - earliestBlockTime) / (latestBlockHeight - earliestBlockHeight) From b1f7b8354a64c2ef647204918c8004b1d7742b6e Mon Sep 17 00:00:00 2001 From: Jon Ator Date: Tue, 4 Jun 2024 16:32:07 -0500 Subject: [PATCH 3/5] Swap tool gas estimation: add more checks to query enabled (#3294) * add more checks to query enabled * improvements --- packages/web/hooks/use-swap.tsx | 38 ++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/packages/web/hooks/use-swap.tsx b/packages/web/hooks/use-swap.tsx index 4b9641e290..2c08677612 100644 --- a/packages/web/hooks/use-swap.tsx +++ b/packages/web/hooks/use-swap.tsx @@ -102,6 +102,7 @@ export function useSwap( const { isOneClickTradingEnabled, oneClickTradingInfo } = useOneClickTradingSession(); const { t } = useTranslation(); + const { isLoading: isWalletLoading } = useWalletSelect(); const swapAssets = useSwapAssets({ initialFromDenom, @@ -119,14 +120,15 @@ export function useSwap( // load flags const isToFromAssets = Boolean(swapAssets.fromAsset) && Boolean(swapAssets.toAsset); - const canLoadQuote = + const quoteQueryEnabled = isToFromAssets && Boolean(inAmountInput.debouncedInAmount?.toDec().isPositive()) && // since input is debounced there could be the wrong asset associated // with the input amount when switching assets inAmountInput.debouncedInAmount?.currency.coinMinimalDenom === swapAssets.fromAsset?.coinMinimalDenom && - !Boolean(account?.txTypeInProgress); + !account?.txTypeInProgress && + !isWalletLoading; const { data: quote, @@ -140,11 +142,11 @@ export function useSwap( forcePoolId: forceSwapInPoolId, maxSlippage, }, - canLoadQuote + quoteQueryEnabled ); /** If a query is not enabled, it is considered loading. * Work around this by checking if the query is enabled and if the query is loading to be considered loading. */ - const isQuoteLoading = isQuoteLoading_ && canLoadQuote; + const isQuoteLoading = isQuoteLoading_ && quoteQueryEnabled; const { data: spotPriceQuote, @@ -196,6 +198,7 @@ export function useSwap( const networkFeeQueryEnabled = featureFlags.swapToolSimulateFee && !Boolean(precedentError) && + // includes check for quoteQueryEnabled !isQuoteLoading && Boolean(quote) && Boolean(account?.address); @@ -211,10 +214,7 @@ export function useSwap( useOneClickTrading: isOneClickTradingEnabled, }, }); - const isLoadingNetworkFee = - featureFlags.swapToolSimulateFee && - isLoadingNetworkFee_ && - networkFeeQueryEnabled; + const isLoadingNetworkFee = isLoadingNetworkFee_ && networkFeeQueryEnabled; const hasExceededOneClickTradingGasLimit = useMemo(() => { if ( @@ -774,6 +774,7 @@ function useSwapAmountInput({ }) { const { chainStore, accountStore } = useStore(); const account = accountStore.getWallet(chainStore.osmosis.chainId); + const { isLoading: isLoadingWallet } = useWalletSelect(); const featureFlags = useFeatureFlags(); @@ -785,10 +786,16 @@ function useSwapAmountInput({ }); const balanceQuoteQueryEnabled = - !!inAmountInput.balance && - !inAmountInput.balance?.toDec().isZero() && + !isLoadingWallet && Boolean(swapAssets.fromAsset) && - Boolean(swapAssets.toAsset); + Boolean(swapAssets.toAsset) && + // since the in amount is debounced, the asset could be wrong when switching assets + inAmountInput.debouncedInAmount?.currency.coinMinimalDenom === + swapAssets.fromAsset!.coinMinimalDenom && + !!inAmountInput.balance && + !inAmountInput.balance.toDec().isZero() && + inAmountInput.balance.currency.coinMinimalDenom === + swapAssets.fromAsset?.coinMinimalDenom; const { data: quoteForCurrentBalance, isLoading: isQuoteForCurrentBalanceLoading_, @@ -806,11 +813,12 @@ function useSwapAmountInput({ const isQuoteForCurrentBalanceLoading = isQuoteForCurrentBalanceLoading_ && balanceQuoteQueryEnabled; - const networkQueryEnabled = + const networkFeeQueryEnabled = featureFlags.swapToolSimulateFee && + // includes check for balanceQuoteQueryEnabled !isQuoteForCurrentBalanceLoading && Boolean(quoteForCurrentBalance) && - !Boolean(account?.txTypeInProgress); + !account?.txTypeInProgress; const { data: currentBalanceNetworkFee, isLoading: isLoadingCurrentBalanceNetworkFee_, @@ -818,10 +826,10 @@ function useSwapAmountInput({ } = useEstimateTxFees({ chainId: chainStore.osmosis.chainId, messages: quoteForCurrentBalance?.messages, - enabled: networkQueryEnabled, + enabled: networkFeeQueryEnabled, }); const isLoadingCurrentBalanceNetworkFee = - networkQueryEnabled && isLoadingCurrentBalanceNetworkFee_; + networkFeeQueryEnabled && isLoadingCurrentBalanceNetworkFee_; const hasErrorWithCurrentBalanceQuote = useMemo(() => { return !!currentBalanceNetworkFeeError || !!quoteForCurrentBalanceError; From 4c054182b56b0959bd61a319715615fd6775aebb Mon Sep 17 00:00:00 2001 From: Matt Upham <30577966+mattupham@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:33:17 -0700 Subject: [PATCH 4/5] Mattupham/fe 478 reduce fe bundle size (#3290) * Remove default exports for spinner and card * Remove chart.ts * Update import for create-pool * remove checkbox-shadcn * Remove select-menu * remove related-assets * Remove promo-drawer * remove use-controllable-prop.ts * remove use-scroll-measure.ts * remove EarnAllocation * update import for token-details * Remove default export for Twitter Section * fix imports for connecting / error wallet states * Fix your balance import * remove noop * Revert "remove noop" This reverts commit 48d938763e3593393a8ef5998646f9691c360820. * Remove position near bounds * Add named export for nomic bridge transfer * Remove price alert * Remove line-chart * Remove swap tool promo * Fix imports * Update typescript * Remove unused translations * remove unused translation * Remove unused translation * Tokens count * Remove method * Update keys - platform * remove scrollToSeeMore key --- packages/bridge/package.json | 2 +- packages/proto-codecs/package.json | 2 +- packages/server/package.json | 2 +- packages/trpc/package.json | 2 +- packages/trpc/src/api.ts | 2 +- packages/tx/package.json | 2 +- .../notifi-alerts/position-near-bounds.tsx | 101 ------ .../assets/notifi-alerts/price-alert.tsx | 68 ---- .../web/components/cards/osmoverse-card.tsx | 2 - .../components/cards/validator-squad-card.tsx | 2 +- .../chart/light-weight-charts/line-chart.ts | 49 --- .../components/complex/add-conc-liquidity.tsx | 2 +- .../components/complex/all-pools-table.tsx | 2 +- .../components/control/checkbox-shadcn.tsx | 29 -- .../web/components/control/select-menu.tsx | 111 ------ .../drawers/token-select-drawer.tsx | 2 +- .../web/components/earn/allocation/index.tsx | 114 ------- .../swap-tool-promo.tsx | 101 ------ packages/web/components/loaders/spinner.tsx | 2 - .../navbar-osmosis-update.tsx | 2 +- .../components/pool-detail/concentrated.tsx | 2 +- .../web/components/related-assets/index.ts | 1 - .../related-assets/related-assets.tsx | 244 ------------- .../web/components/swap-tool/promo-drawer.tsx | 43 --- .../web/components/table/asset-balances.tsx | 2 +- packages/web/components/table/asset-info.tsx | 2 +- .../token-details/token-details.tsx | 14 +- .../twitter-section/twitter-section.tsx | 4 +- .../wallet-states/connecting-wallet-state.tsx | 4 +- .../wallet-states/error-wallet-state.tsx | 4 +- .../components/your-balance/your-balance.tsx | 4 +- packages/web/hooks/use-controllable-prop.ts | 11 - packages/web/hooks/use-scroll-measure.ts | 66 ---- packages/web/integrations/nomic/transfer.tsx | 4 +- .../fetched-card/history-rows.tsx | 2 +- .../notifi-subscription-card/loading-card.tsx | 2 +- packages/web/localizations/de.json | 12 - packages/web/localizations/en.json | 12 - packages/web/localizations/es.json | 12 - packages/web/localizations/fa.json | 12 - packages/web/localizations/fr.json | 12 - packages/web/localizations/gu.json | 12 - packages/web/localizations/hi.json | 12 - packages/web/localizations/ja.json | 12 - packages/web/localizations/ko.json | 12 - packages/web/localizations/pl.json | 12 - packages/web/localizations/pt-br.json | 12 - packages/web/localizations/ro.json | 12 - packages/web/localizations/ru.json | 12 - packages/web/localizations/tr.json | 12 - packages/web/localizations/zh-cn.json | 12 - packages/web/localizations/zh-hk.json | 12 - packages/web/localizations/zh-tw.json | 12 - packages/web/modals/bridge-transfer-v1.tsx | 12 +- packages/web/modals/create-pool.tsx | 2 +- packages/web/modals/profile.tsx | 2 +- .../wallet-select/cosmos-wallet-state.tsx | 4 +- .../modals/wallet-select/evm-wallet-state.tsx | 4 +- packages/web/package.json | 5 +- packages/web/pages/assets/[denom].tsx | 14 +- packages/web/pages/components.tsx | 2 +- packages/web/pages/earn/index.tsx | 30 +- packages/web/utils/chart.ts | 17 - packages/web/utils/react-context.ts | 3 +- yarn.lock | 320 +++++++++++++++++- 65 files changed, 360 insertions(+), 1267 deletions(-) delete mode 100644 packages/web/components/assets/notifi-alerts/position-near-bounds.tsx delete mode 100644 packages/web/components/assets/notifi-alerts/price-alert.tsx delete mode 100644 packages/web/components/chart/light-weight-charts/line-chart.ts delete mode 100644 packages/web/components/control/checkbox-shadcn.tsx delete mode 100644 packages/web/components/control/select-menu.tsx delete mode 100644 packages/web/components/earn/allocation/index.tsx delete mode 100644 packages/web/components/funnels/concentrated-liquidity/swap-tool-promo.tsx delete mode 100644 packages/web/components/related-assets/index.ts delete mode 100644 packages/web/components/related-assets/related-assets.tsx delete mode 100644 packages/web/components/swap-tool/promo-drawer.tsx delete mode 100644 packages/web/hooks/use-controllable-prop.ts delete mode 100644 packages/web/hooks/use-scroll-measure.ts delete mode 100644 packages/web/utils/chart.ts diff --git a/packages/bridge/package.json b/packages/bridge/package.json index 99f1c6a595..2d7cb64e8c 100644 --- a/packages/bridge/package.json +++ b/packages/bridge/package.json @@ -46,7 +46,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.0", - "typescript": "^5.4.3" + "typescript": "^5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/proto-codecs/package.json b/packages/proto-codecs/package.json index 0a9e3c8c77..041b8f419d 100644 --- a/packages/proto-codecs/package.json +++ b/packages/proto-codecs/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@cosmology/proto-parser": "^1.5.0", "@cosmology/telescope": "^1.5.1", - "@types/node": "^18.16.3", + "@types/node": "^20.14.1", "regenerator-runtime": "^0.13.11", "rimraf": "^5.0.0", "tsx": "^4.6.2" diff --git a/packages/server/package.json b/packages/server/package.json index 0fe3b5c14f..1910a49735 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -51,7 +51,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.0", - "typescript": "^5.4.3" + "typescript": "^5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/trpc/package.json b/packages/trpc/package.json index db8a63d27f..1896454d0e 100644 --- a/packages/trpc/package.json +++ b/packages/trpc/package.json @@ -46,7 +46,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.0", - "typescript": "^5.4.3" + "typescript": "^5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/trpc/src/api.ts b/packages/trpc/src/api.ts index 74258bc260..a1e594348b 100644 --- a/packages/trpc/src/api.ts +++ b/packages/trpc/src/api.ts @@ -18,7 +18,7 @@ import { trpcMiddleware } from "./middleware"; /** * Pass asset lists and chain list to be used cas context in backend service. */ -export type CreateContextOptions = { +type CreateContextOptions = { assetLists: AssetList[]; chainList: Chain[]; }; diff --git a/packages/tx/package.json b/packages/tx/package.json index 11a68e99f6..99b4337291 100644 --- a/packages/tx/package.json +++ b/packages/tx/package.json @@ -41,7 +41,7 @@ "@types/jest-in-case": "^1.0.6", "jest-in-case": "^1.0.2", "ts-jest": "^29.1.0", - "typescript": "^5.4.3" + "typescript": "^5.4.5" }, "lint-staged": { "*": [ diff --git a/packages/web/components/assets/notifi-alerts/position-near-bounds.tsx b/packages/web/components/assets/notifi-alerts/position-near-bounds.tsx deleted file mode 100644 index b2c8ff79fd..0000000000 --- a/packages/web/components/assets/notifi-alerts/position-near-bounds.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { FunctionComponent } from "react"; - -export const PositionNearBoundsIcon: FunctionComponent<{ - className?: string; -}> = ({ className }) => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/packages/web/components/assets/notifi-alerts/price-alert.tsx b/packages/web/components/assets/notifi-alerts/price-alert.tsx deleted file mode 100644 index 9f4dc62cb0..0000000000 --- a/packages/web/components/assets/notifi-alerts/price-alert.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { FunctionComponent } from "react"; - -export const PriceAlertIcon: FunctionComponent<{ className?: string }> = ({ - className, -}) => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/packages/web/components/cards/osmoverse-card.tsx b/packages/web/components/cards/osmoverse-card.tsx index 57e4ea1b85..b41cec82dc 100644 --- a/packages/web/components/cards/osmoverse-card.tsx +++ b/packages/web/components/cards/osmoverse-card.tsx @@ -24,5 +24,3 @@ export const OsmoverseCard: React.FC = ({ ); - -export default OsmoverseCard; diff --git a/packages/web/components/cards/validator-squad-card.tsx b/packages/web/components/cards/validator-squad-card.tsx index e224f433e3..8bdc65fb42 100644 --- a/packages/web/components/cards/validator-squad-card.tsx +++ b/packages/web/components/cards/validator-squad-card.tsx @@ -6,7 +6,7 @@ import React from "react"; import { useCallback, useMemo } from "react"; import { FallbackImg } from "~/components/assets"; -import OsmoverseCard from "~/components/cards/osmoverse-card"; +import { OsmoverseCard } from "~/components/cards/osmoverse-card"; import { Tooltip } from "~/components/tooltip"; import { Button } from "~/components/ui/button"; import { Breakpoint, useTranslation, useWindowSize } from "~/hooks"; diff --git a/packages/web/components/chart/light-weight-charts/line-chart.ts b/packages/web/components/chart/light-weight-charts/line-chart.ts deleted file mode 100644 index 58fdcf2c8b..0000000000 --- a/packages/web/components/chart/light-weight-charts/line-chart.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { - DeepPartial, - ISeriesApi, - LineData, - LineSeriesOptions, - LineStyleOptions, - SeriesOptionsCommon, - Time, - TimeChartOptions, - WhitespaceData, -} from "lightweight-charts"; - -import { ChartController, ChartControllerParams } from "./chart-controller"; - -export class LineChartController< - T = TimeChartOptions, - K = Time -> extends ChartController { - series: ISeriesApi< - "Line", - Time, - LineData