diff --git a/.github/workflows/monitoring-e2e-tests.yml b/.github/workflows/monitoring-e2e-tests.yml index f02df28070..fcc4b8bd47 100644 --- a/.github/workflows/monitoring-e2e-tests.yml +++ b/.github/workflows/monitoring-e2e-tests.yml @@ -16,7 +16,7 @@ jobs: echo "matrix={\"include\":[{ \"base-url\":\"https://app.osmosis.zone\", \"server-url\":\"https://sqs.osmosis.zone\", \"env\": \"production\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}, { \"base-url\":\"https://stage.osmosis.zone\", \"server-url\":\"https://sqs.stage.osmosis.zone\", \"env\": \"staging\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}]}" >> "$GITHUB_OUTPUT" frontend-e2e-tests: - name: production + name: production-swap runs-on: macos-latest environment: name: prod_swap_test diff --git a/packages/bridge/src/axelar/index.ts b/packages/bridge/src/axelar/index.ts index 79480caefb..f5a775d657 100644 --- a/packages/bridge/src/axelar/index.ts +++ b/packages/bridge/src/axelar/index.ts @@ -170,7 +170,7 @@ export class AxelarBridgeProvider implements BridgeProvider { ) { throw new BridgeQuoteError({ bridgeId: AxelarBridgeProvider.ID, - errorType: "UnsupportedQuoteError", + errorType: "InsufficientAmountError", message: `Negative output amount ${new IntPretty( expectedOutputAmount ).trim(true)} for asset in: ${new IntPretty(fromAmount).trim( @@ -345,7 +345,7 @@ export class AxelarBridgeProvider implements BridgeProvider { return foundVariants.assets; } catch (e) { // Avoid returning options if there's an unexpected error, such as the provider being down - if (process.env.NODE_ENV === "development") { + if (process.env.NODE_ENV !== "production") { console.error( AxelarBridgeProvider.ID, "failed to get supported assets:", @@ -413,6 +413,21 @@ export class AxelarBridgeProvider implements BridgeProvider { ], }, bech32Address: params.fromAddress, + }).catch((e) => { + if ( + e instanceof Error && + e.message.includes( + "No fee tokens found with sufficient balance on account" + ) + ) { + throw new BridgeQuoteError({ + bridgeId: AxelarBridgeProvider.ID, + errorType: "InsufficientAmountError", + message: e.message, + }); + } + + throw e; }); const gasFee = txSimulation.amount[0]; diff --git a/packages/bridge/src/ibc/index.ts b/packages/bridge/src/ibc/index.ts index dc13359301..8bae5562c7 100644 --- a/packages/bridge/src/ibc/index.ts +++ b/packages/bridge/src/ibc/index.ts @@ -63,6 +63,21 @@ export class IbcBridgeProvider implements BridgeProvider { ], }, bech32Address: params.fromAddress, + }).catch((e) => { + if ( + e instanceof Error && + e.message.includes( + "No fee tokens found with sufficient balance on account" + ) + ) { + throw new BridgeQuoteError({ + bridgeId: IbcBridgeProvider.ID, + errorType: "InsufficientAmountError", + message: e.message, + }); + } + + throw e; }); const gasFee = txSimulation.amount[0]; const gasAsset = this.getGasAsset(fromChainId, gasFee.denom); @@ -124,7 +139,7 @@ export class IbcBridgeProvider implements BridgeProvider { ]; } catch (e) { // Avoid returning options if there's an unexpected error, such as the provider being down - if (process.env.NODE_ENV === "development") { + if (process.env.NODE_ENV !== "production") { console.error( IbcBridgeProvider.ID, "failed to get supported assets:", diff --git a/packages/bridge/src/skip/index.ts b/packages/bridge/src/skip/index.ts index a7063cefee..4a7e5910dd 100644 --- a/packages/bridge/src/skip/index.ts +++ b/packages/bridge/src/skip/index.ts @@ -112,13 +112,43 @@ export class SkipBridgeProvider implements BridgeProvider { }); } - const route = await this.skipClient.route({ - source_asset_denom: sourceAsset.denom, - source_asset_chain_id: fromChain.chainId.toString(), - dest_asset_denom: destinationAsset.denom, - dest_asset_chain_id: toChain.chainId.toString(), - amount_in: fromAmount, - }); + const route = await this.skipClient + .route({ + source_asset_denom: sourceAsset.denom, + source_asset_chain_id: fromChain.chainId.toString(), + dest_asset_denom: destinationAsset.denom, + dest_asset_chain_id: toChain.chainId.toString(), + amount_in: fromAmount, + }) + .catch((e) => { + if (e instanceof Error) { + const msg = e.message; + if ( + msg.includes( + "Input amount is too low to cover" + // Could be Axelar or CCTP + ) + ) { + throw new BridgeQuoteError({ + bridgeId: SkipBridgeProvider.ID, + errorType: "InsufficientAmountError", + message: msg, + }); + } + if ( + msg.includes( + "cannot transfer across cctp after route demands swap" + ) + ) { + throw new BridgeQuoteError({ + bridgeId: SkipBridgeProvider.ID, + errorType: "NoQuotesError", + message: msg, + }); + } + } + throw e; + }); const addressList = await this.getAddressList( route.chain_ids, @@ -234,11 +264,25 @@ export class SkipBridgeProvider implements BridgeProvider { a.coinMinimalDenom.toLowerCase() === asset.address.toLowerCase() ); - for (const counterparty of assetListAsset?.counterparty ?? []) { + const counterparties = assetListAsset?.counterparty ?? []; + // since skip supports cosmos swap, we can include other asset list + // counterparties of the same variant + if (assetListAsset) { + const variantAssets = this.ctx.assetLists.flatMap(({ assets }) => + assets.filter( + (asset) => asset.variantGroupKey === assetListAsset.variantGroupKey + ) + ); + counterparties.push( + ...variantAssets.flatMap((asset) => asset.counterparty) + ); + } + + for (const counterparty of counterparties) { // check if supported by skip if (!("chainId" in counterparty)) continue; if ( - !assets[counterparty.chainId].assets.some( + !assets[counterparty.chainId]?.assets.some( (a) => a.denom.toLowerCase() === counterparty.sourceDenom.toLowerCase() ) @@ -334,7 +378,7 @@ export class SkipBridgeProvider implements BridgeProvider { return foundVariants.assets; } catch (e) { // Avoid returning options if there's an unexpected error, such as the provider being down - if (process.env.NODE_ENV === "development") { + if (process.env.NODE_ENV !== "production") { console.error( SkipBridgeProvider.ID, "failed to get supported assets:", @@ -818,6 +862,21 @@ export class SkipBridgeProvider implements BridgeProvider { ], }, bech32Address: params.fromAddress, + }).catch((e) => { + if ( + e instanceof Error && + e.message.includes( + "No fee tokens found with sufficient balance on account" + ) + ) { + throw new BridgeQuoteError({ + bridgeId: SkipBridgeProvider.ID, + errorType: "InsufficientAmountError", + message: e.message, + }); + } + + throw e; }); const gasFee = txSimulation.amount[0]; diff --git a/packages/bridge/src/squid/error.ts b/packages/bridge/src/squid/error.ts new file mode 100644 index 0000000000..cdecb53c0a --- /dev/null +++ b/packages/bridge/src/squid/error.ts @@ -0,0 +1,36 @@ +import { ApiClientError } from "@osmosis-labs/utils"; +import { z } from "zod"; + +const SquidErrors = z.object({ + errors: z.array( + z.object({ + path: z.string().optional(), + errorType: z.string(), + message: z.string(), + }) + ), +}); + +type SquidErrors = z.infer; + +/** + * Squid returns error data in the form of an errors object containing an array of errors. + * This function returns the list of those errors, and sets the Error.message to a concatenation of those errors. + * @returns list of error messages + */ +export function getSquidErrors(error: ApiClientError): SquidErrors { + try { + const e = error as ApiClientError; + const squidError = SquidErrors.parse(e.data); + const msgs = squidError.errors.map( + ({ message }, i) => `${i + 1}) ${message}` + ); + e.message = msgs.join(", "); + return squidError; + } catch (e) { + if (e instanceof z.ZodError) { + throw new Error("Squid error validation failed:" + e.errors.join(", ")); + } + throw new Error("Squid errors: An unexpected error occurred"); + } +} diff --git a/packages/bridge/src/squid/index.ts b/packages/bridge/src/squid/index.ts index 4627e63e15..9e05a29a06 100644 --- a/packages/bridge/src/squid/index.ts +++ b/packages/bridge/src/squid/index.ts @@ -45,6 +45,7 @@ import { } from "../interface"; import { cosmosMsgOpts, cosmwasmMsgOpts } from "../msg"; import { BridgeAssetMap } from "../utils"; +import { getSquidErrors } from "./error"; const IbcTransferType = "/ibc.applications.transfer.v1.MsgTransfer"; const WasmTransferType = "/cosmwasm.wasm.v1.MsgExecuteContract"; @@ -119,6 +120,26 @@ export class SquidBridgeProvider implements BridgeProvider { headers: { "x-integrator-id": this.integratorId, }, + }).catch((e) => { + if (e instanceof ApiClientError) { + const errMsgs = getSquidErrors(e); + + if ( + errMsgs.errors.some(({ message }) => + message.includes( + "The input amount is not high enough to cover the bridge fee" + ) + ) + ) { + throw new BridgeQuoteError({ + bridgeId: SquidBridgeProvider.ID, + errorType: "InsufficientAmountError", + message: e.message, + }); + } + } + + throw e; }); const { @@ -349,7 +370,7 @@ export class SquidBridgeProvider implements BridgeProvider { return foundVariants.assets; } catch (e) { // Avoid returning options if there's an unexpected error, such as the provider being down - if (process.env.NODE_ENV === "development") { + if (process.env.NODE_ENV !== "production") { console.error( SquidBridgeProvider.ID, "failed to get supported assets:", diff --git a/packages/bridge/src/wormhole/index.ts b/packages/bridge/src/wormhole/index.ts index b37e0775f3..8208ae2c95 100644 --- a/packages/bridge/src/wormhole/index.ts +++ b/packages/bridge/src/wormhole/index.ts @@ -63,20 +63,20 @@ export class WormholeBridgeProvider implements BridgeProvider { fromAsset, toAsset, }: GetBridgeExternalUrlParams): Promise { - // For now we use in-osmosis - const url = new URL("https://app.osmosis.zone/wormhole"); + // For now we use Portal Bridge + const url = new URL("https://portalbridge.com/"); url.searchParams.set( - "from", + "sourceChain", fromChain.chainName?.toLowerCase() ?? fromChain.chainId.toString() ); url.searchParams.set( - "to", + "targetChain", toChain.chainName?.toLowerCase() ?? toChain.chainId.toString() ); url.searchParams.set( - "token", - fromChain.chainType === "solana" ? fromAsset.denom : toAsset.denom + "asset", + fromChain.chainType === "solana" ? fromAsset.address : toAsset.address ); return { diff --git a/packages/web/components/bridge/amount-and-review-screen.tsx b/packages/web/components/bridge/amount-and-review-screen.tsx index 587ab5a8ae..95262f4297 100644 --- a/packages/web/components/bridge/amount-and-review-screen.tsx +++ b/packages/web/components/bridge/amount-and-review-screen.tsx @@ -1,4 +1,5 @@ import { CoinPretty } from "@keplr-wallet/unit"; +import type { Bridge } from "@osmosis-labs/bridge"; import { isNil, noop } from "@osmosis-labs/utils"; import { observer } from "mobx-react-lite"; import { useMemo, useState } from "react"; @@ -14,7 +15,10 @@ import { AmountScreen } from "./amount-screen"; import { ImmersiveBridgeScreen } from "./immersive-bridge"; import { ReviewScreen } from "./review-screen"; import { QuotableBridge, useBridgeQuotes } from "./use-bridge-quotes"; -import { SupportedAsset } from "./use-bridges-supported-assets"; +import { + SupportedAsset, + useBridgesSupportedAssets, +} from "./use-bridges-supported-assets"; export type SupportedAssetWithAmount = SupportedAsset & { amount: CoinPretty }; @@ -78,17 +82,51 @@ export const AmountAndReviewScreen = observer( ? evmConnector?.icon : toChainCosmosAccount?.walletInfo.logo; + const { data: assetsInOsmosis } = + api.edge.assets.getCanonicalAssetWithVariants.useQuery( + { + findMinDenomOrSymbol: selectedAssetDenom ?? "", + }, + { + enabled: !isNil(selectedAssetDenom), + cacheTime: 10 * 60 * 1000, // 10 minutes + staleTime: 10 * 60 * 1000, // 10 minutes + } + ); + + const supportedAssets = useBridgesSupportedAssets({ + assets: assetsInOsmosis, + chain: { + chainId: accountStore.osmosisChainId, + chainType: "cosmos", + }, + }); + const { supportedAssetsByChainId: counterpartySupportedAssetsByChainId } = + supportedAssets; + /** Filter for bridges that currently support quoting. */ const quoteBridges = useMemo(() => { - const assetSupportedBridges = - (direction === "deposit" - ? fromAsset?.supportedVariants[toAsset?.address ?? ""] - : toAsset?.supportedVariants[fromAsset?.address ?? ""]) ?? []; + const assetSupportedBridges = new Set(); + + if (direction === "deposit" && fromAsset) { + Object.values(fromAsset.supportedVariants) + .flat() + .forEach((provider) => assetSupportedBridges.add(provider)); + } else if (direction === "withdraw" && fromAsset && toAsset) { + // withdraw + counterpartySupportedAssetsByChainId[toAsset.chainId].forEach( + (asset) => { + asset.supportedVariants[fromAsset.address]?.forEach((provider) => { + assetSupportedBridges.add(provider); + }); + } + ); + } - return assetSupportedBridges.filter( + return Array.from(assetSupportedBridges).filter( (bridge) => bridge !== "Nomic" && bridge !== "Wormhole" ) as QuotableBridge[]; - }, [direction, fromAsset, toAsset]); + }, [direction, fromAsset, toAsset, counterpartySupportedAssetsByChainId]); const quote = useBridgeQuotes({ toAddress, @@ -141,6 +179,8 @@ export const AmountAndReviewScreen = observer( ; + fromChain: BridgeChainWithDisplayInfo | undefined; setFromChain: (chain: BridgeChainWithDisplayInfo) => void; toChain: BridgeChainWithDisplayInfo | undefined; @@ -100,6 +103,12 @@ export const AmountScreen = observer( direction, selectedDenom, + assetsInOsmosis, + bridgesSupportedAssets: { + supportedAssetsByChainId: counterpartySupportedAssetsByChainId, + supportedChains, + }, + fromChain, setFromChain, toChain, @@ -199,18 +208,6 @@ export const AmountScreen = observer( ? evmAddress : toCosmosCounterpartyAccount?.address; - const { data: assetsInOsmosis } = - api.edge.assets.getCanonicalAssetWithVariants.useQuery( - { - findMinDenomOrSymbol: selectedDenom!, - }, - { - enabled: !isNil(selectedDenom), - cacheTime: 10 * 60 * 1000, // 10 minutes - staleTime: 10 * 60 * 1000, // 10 minutes - } - ); - const { data: osmosisChain } = api.edge.chains.getChain.useQuery({ findChainNameOrId: accountStore.osmosisChainId, }); @@ -229,17 +226,6 @@ export const AmountScreen = observer( canonicalAsset ); - const { - supportedAssetsByChainId: counterpartySupportedAssetsByChainId, - supportedChains, - } = useBridgesSupportedAssets({ - assets: assetsInOsmosis, - chain: { - chainId: accountStore.osmosisChainId, - chainType: "cosmos", - }, - }); - const firstSupportedEvmChain = useMemo( () => supportedChains.find( @@ -702,6 +688,11 @@ export const AmountScreen = observer( isInsufficientBal={Boolean(isInsufficientBal)} isInsufficientFee={Boolean(isInsufficientFee)} transferGasCost={selectedQuote?.gasCost} + /** Wait for all quotes to resolve before modifying input amount. + * This helps reduce thrash while the best quote is being determined. + * Only once we get the best quote, we can modify the input amount + * to account for gas then restart the quote search process. */ + canSetMax={!quote.isLoadingAnyBridgeQuote} setFiatAmount={setFiatAmount} setCryptoAmount={setCryptoAmount} setInputUnit={setInputUnit} @@ -886,7 +877,8 @@ export const AmountScreen = observer( {(direction === "deposit" ? !isNil(fromAsset) && Object.keys(fromAsset.supportedVariants).length > 1 - : !isNil(toAsset) && + : // direction === "withdraw" + !isNil(toAsset) && counterpartySupportedAssetsByChainId[toAsset.chainId]?.length > 1) && ( diff --git a/packages/web/components/bridge/crypto-fiat-input.tsx b/packages/web/components/bridge/crypto-fiat-input.tsx index b4084684ce..f6373a7452 100644 --- a/packages/web/components/bridge/crypto-fiat-input.tsx +++ b/packages/web/components/bridge/crypto-fiat-input.tsx @@ -37,6 +37,7 @@ export const CryptoFiatInput: FunctionComponent<{ isInsufficientFee: boolean; fromChain: BridgeChainWithDisplayInfo; transferGasCost: CoinPretty | undefined; + canSetMax?: boolean; setFiatAmount: (amount: string) => void; setCryptoAmount: (amount: string) => void; setInputUnit: (unit: "fiat" | "crypto") => void; @@ -50,6 +51,7 @@ export const CryptoFiatInput: FunctionComponent<{ isInsufficientBal, isInsufficientFee, transferGasCost, + canSetMax = true, setFiatAmount: setFiatAmountProp, setCryptoAmount: setCryptoAmountProp, setInputUnit, @@ -152,7 +154,7 @@ export const CryptoFiatInput: FunctionComponent<{ // Subtract gas cost and adjust input when selecting max amount useEffect(() => { - if (isMax && transferGasCost) { + if (isMax && transferGasCost && canSetMax) { let maxTransferAmount = new Dec(0); const gasFeeMatchesInputDenom = @@ -175,14 +177,14 @@ export const CryptoFiatInput: FunctionComponent<{ onInput("crypto")(trimPlaceholderZeros(maxTransferAmount.toString())); } } - }, [isMax, transferGasCost, asset.amount, inputCoin, onInput]); + }, [isMax, canSetMax, transferGasCost, asset.amount, inputCoin, onInput]); // Apply max amount if asset changes useEffect(() => { - if (isMax) { + if (isMax && canSetMax) { onInput("crypto")(trimPlaceholderZeros(asset.amount.toDec().toString())); } - }, [asset, isMax, onInput]); + }, [asset, isMax, canSetMax, onInput]); const fiatCurrentValue = `${assetPrice.symbol}${fiatInputRaw}`; const fiatInputFontSize = calcTextSizeClass( diff --git a/packages/web/components/bridge/immersive-bridge.tsx b/packages/web/components/bridge/immersive-bridge.tsx index 56412e3cbe..269d133539 100644 --- a/packages/web/components/bridge/immersive-bridge.tsx +++ b/packages/web/components/bridge/immersive-bridge.tsx @@ -142,7 +142,7 @@ export const ImmersiveBridgeFlow = ({ /> )} -
+
{ - if ( - someError?.message.includes( - "No fee tokens found with sufficient balance on account" - ) || - someError?.message.includes( - "Input amount is too low to cover CCTP bridge relay fee" - ) || - someError?.message.includes( - "cannot transfer across cctp after route demands swap" - ) - ) + if (someError?.message.includes("InsufficientAmountError" as BridgeError)) return true; if (!inputCoin || !selectedQuote || !selectedQuote.gasCost) return false; @@ -401,21 +391,6 @@ export const useBridgeQuotes = ({ return false; }, [someError, inputCoin, selectedQuote]); - // const isInsufficientFee = - // // Cosmos not fee tokens error - // someError?.message.includes( - // "No fee tokens found with sufficient balance on account" - // ) || - // (inputAmountRaw !== "" && - // availableBalance && - // selectedQuote?.gasCost !== undefined && - // selectedQuote.gasCost.denom === availableBalance.denom && // make sure the fee is in the same denom as the asset - // inputCoin - // ?.toDec() - // .sub(availableBalance.toDec()) // subtract by available balance to get the maximum transfer amount - // .abs() - // .lt(selectedQuote.gasCost.toDec())); - const bridgeTransaction = api.bridgeTransfer.getTransactionRequestByBridge.useQuery( { @@ -678,17 +653,25 @@ export const useBridgeQuotes = ({ (!isOneSuccessful || quoteResults.every((quoteResult) => quoteResult.isLoading)) && quoteResults.some((quoteResult) => quoteResult.fetchStatus !== "idle"); + const isLoadingAnyBridgeQuote = quoteResults.some( + (quoteResult) => quoteResult.isLoading && quoteResult.fetchStatus !== "idle" + ); const isLoadingBridgeTransaction = bridgeTransaction.isLoading && bridgeTransaction.fetchStatus !== "idle"; - const isWithdrawReady = isWithdraw && !isTxPending; - const isWalletConnected = + const isWithdrawReady = + isWithdraw && !isTxPending && !isLoadingBridgeTransaction; + const isFromWalletConnected = fromChain?.chainType === "evm" ? isEvmWalletConnected : fromChain?.chainType === "cosmos" ? accountStore.getWallet(fromChain.chainId)?.isWalletConnected ?? false : false; const isDepositReady = - isDeposit && isWalletConnected && !isLoadingBridgeQuote && !isTxPending; + isDeposit && + isFromWalletConnected && + !isLoadingBridgeQuote && + !isTxPending && + !isLoadingBridgeTransaction; const userCanAdvance = (isDepositReady || isWithdrawReady) && !isInsufficientFee && @@ -736,6 +719,7 @@ export const useBridgeQuotes = ({ warnUserOfPriceImpact, successfulQuotes, + isAllQuotesSuccessful: isAllSuccessful, selectedBridgeProvider, setSelectedBridgeProvider: onChangeBridgeProvider, @@ -743,6 +727,7 @@ export const useBridgeQuotes = ({ selectedQuoteUpdatedAt: selectedQuoteQuery?.dataUpdatedAt, refetchInterval, isLoadingBridgeQuote, + isLoadingAnyBridgeQuote, isLoadingBridgeTransaction, isRefetchingQuote: selectedQuoteQuery?.isRefetching ?? false, }; diff --git a/packages/web/e2e/pages/portfolio-page.ts b/packages/web/e2e/pages/portfolio-page.ts index f4f130047a..7a9cec822d 100644 --- a/packages/web/e2e/pages/portfolio-page.ts +++ b/packages/web/e2e/pages/portfolio-page.ts @@ -31,19 +31,9 @@ export class PortfolioPage extends BasePage { console.log("FE opened at: " + currentUrl); } - async hideZeroBalances() { - await this.hideZeros.click(); - await this.page.waitForTimeout(1000); - } - - async viewMoreBalances() { - await this.viewMore.click(); - await this.page.waitForTimeout(1000); - } - async getBalanceFor(token: string) { const bal = this.page - .locator(`//tbody/tr//a[@href="/assets/${token}"]`) + .locator(`//tbody/tr//a[@href="/assets/${token}?ref=portfolio"]`) .nth(1); let tokenBalance: string = await bal.innerText(); console.log(`Balance for ${token}: ${tokenBalance}`); diff --git a/packages/web/e2e/pages/transactions-page.ts b/packages/web/e2e/pages/transactions-page.ts index 19da7126a5..3ce069878f 100644 --- a/packages/web/e2e/pages/transactions-page.ts +++ b/packages/web/e2e/pages/transactions-page.ts @@ -15,6 +15,11 @@ export class TransactionsPage extends BasePage { this.closeTransactionBtn = page.getByLabel("Close").nth(1); } + async open() { + await this.page.goto("/transactions"); + return this; + } + async viewTransactionByNumber(number: number) { await this.transactionRow.nth(number).click(); await this.page.waitForTimeout(1000); diff --git a/packages/web/e2e/tests/portfolio.wallet.spec.ts b/packages/web/e2e/tests/portfolio.wallet.spec.ts index 24f9439aef..0a78bc81c9 100644 --- a/packages/web/e2e/tests/portfolio.wallet.spec.ts +++ b/packages/web/e2e/tests/portfolio.wallet.spec.ts @@ -10,7 +10,7 @@ import { WalletPage } from "../pages/wallet-page"; test.describe("Test Portfolio feature", () => { let context: BrowserContext; - const privateKey = process.env.PRIVATE_KEY ?? "private_key"; + const privateKey = process.env.PRIVATE_KEY ?? "pk"; const password = process.env.PASSWORD ?? "TestPassword2024."; let portfolioPage: PortfolioPage; let dollarBalanceRegEx = /\$\d+/; @@ -49,8 +49,6 @@ test.describe("Test Portfolio feature", () => { portfolioPage = new PortfolioPage(page); await portfolioPage.goto(); await portfolioPage.connectWallet(); - await portfolioPage.hideZeroBalances(); - await portfolioPage.viewMoreBalances(); }); test.afterAll(async () => { @@ -85,6 +83,10 @@ test.describe("Test Portfolio feature", () => { expect(ethBalance).toMatch(dollarBalanceRegEx); const kujiBalance = await portfolioPage.getBalanceFor("KUJI"); expect(kujiBalance).toMatch(dollarBalanceRegEx); + const solBalance = await portfolioPage.getBalanceFor("SOL"); + expect(solBalance).toMatch(dollarBalanceRegEx); + const milkTIABalance = await portfolioPage.getBalanceFor("milkTIA"); + expect(milkTIABalance).toMatch(dollarBalanceRegEx); const abtcBalance = await portfolioPage.getBalanceFor("allBTC"); // allBTC has not $ price atm expect(abtcBalance).toMatch(digitBalanceRegEx); diff --git a/packages/web/e2e/tests/swap.stables.spec.ts b/packages/web/e2e/tests/swap.stables.spec.ts index a31c97daf3..a8d1ebeec3 100644 --- a/packages/web/e2e/tests/swap.stables.spec.ts +++ b/packages/web/e2e/tests/swap.stables.spec.ts @@ -60,9 +60,9 @@ test.describe("Test Swap Stables feature", () => { await context.close(); }); - test("User should be able to swap USDC to USDC.axl", async () => { + test("User should be able to swap USDC to USDC.eth.axl", async () => { await swapPage.goto(); - await swapPage.selectPair("USDC", "USDC.axl"); + await swapPage.selectPair("USDC", "USDC.eth.axl"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); @@ -75,9 +75,9 @@ test.describe("Test Swap Stables feature", () => { expect(swapPage.getTransactionUrl()).toBeTruthy(); }); - test("User should be able to swap USDC.axl to USDC", async () => { + test("User should be able to swap USDC.eth.axl to USDC", async () => { await swapPage.goto(); - await swapPage.selectPair("USDC.axl", "USDC"); + await swapPage.selectPair("USDC.eth.axl", "USDC"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); @@ -90,9 +90,9 @@ test.describe("Test Swap Stables feature", () => { expect(swapPage.getTransactionUrl()).toBeTruthy(); }); - test("User should be able to swap USDT to USDT.axl", async () => { + test("User should be able to swap USDT to USDT.eth.axl", async () => { await swapPage.goto(); - await swapPage.selectPair("USDT", "USDT.axl"); + await swapPage.selectPair("USDT", "USDT.eth.axl"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); @@ -105,9 +105,9 @@ test.describe("Test Swap Stables feature", () => { expect(swapPage.getTransactionUrl()).toBeTruthy(); }); - test("User should be able to swap USDT.axl to USDT", async () => { + test("User should be able to swap USDT.eth.axl to USDT", async () => { await swapPage.goto(); - await swapPage.selectPair("USDT.axl", "USDT"); + await swapPage.selectPair("USDT.eth.axl", "USDT"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); @@ -120,9 +120,9 @@ test.describe("Test Swap Stables feature", () => { expect(swapPage.getTransactionUrl()).toBeTruthy(); }); - test("User should be able to swap USDT.axl to USDT.kava", async () => { + test("User should be able to swap USDT.eth.axl to USDT.kava", async () => { await swapPage.goto(); - await swapPage.selectPair("USDT.axl", "USDT.kava"); + await swapPage.selectPair("USDT.eth.axl", "USDT.kava"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); @@ -135,9 +135,9 @@ test.describe("Test Swap Stables feature", () => { expect(swapPage.getTransactionUrl()).toBeTruthy(); }); - test("User should be able to swap USDT.kava to USDT.axl", async () => { + test("User should be able to swap USDT.kava to USDT.eth.axl", async () => { await swapPage.goto(); - await swapPage.selectPair("USDT.kava", "USDT.axl"); + await swapPage.selectPair("USDT.kava", "USDT.eth.axl"); await swapPage.enterAmount("0.1"); await swapPage.showSwapInfo(); const { msgContentAmount } = await swapPage.swapAndGetWalletMsg(context); diff --git a/packages/web/e2e/tests/transactions.wallet.spec.ts b/packages/web/e2e/tests/transactions.wallet.spec.ts index 9f95fab2c4..fc379143b6 100644 --- a/packages/web/e2e/tests/transactions.wallet.spec.ts +++ b/packages/web/e2e/tests/transactions.wallet.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { BrowserContext, chromium, expect, test } from "@playwright/test"; +import { BrowserContext, chromium, expect, Page, test } from "@playwright/test"; import process from "process"; import { SwapPage } from "~/e2e/pages/swap-page"; @@ -11,9 +11,10 @@ import { WalletPage } from "../pages/wallet-page"; test.describe("Test Transactions feature", () => { let context: BrowserContext; + let page: Page; const walletId = process.env.WALLET_ID ?? "osmo1ka7q9tykdundaanr07taz3zpt5k72c0ut5r4xa"; - const privateKey = process.env.PRIVATE_KEY ?? "private_key"; + const privateKey = process.env.PRIVATE_KEY ?? "pk"; const password = process.env.PASSWORD ?? "TestPassword2024."; let portfolioPage: PortfolioPage; let transactionsPage: TransactionsPage; @@ -36,7 +37,7 @@ test.describe("Test Transactions feature", () => { // Get all new pages (including Extension) in the context and wait const emptyPage = context.pages()[0]; await emptyPage.waitForTimeout(2000); - const page = context.pages()[1]; + page = context.pages()[1]; const walletPage = new WalletPage(page); // Import existing Wallet (could be aggregated in one function). await walletPage.importWalletWithPrivateKey(privateKey); @@ -44,10 +45,11 @@ test.describe("Test Transactions feature", () => { await walletPage.selectChainsAndSave(); await walletPage.finish(); // Switch to Application - portfolioPage = new PortfolioPage(context.pages()[0]); + page = context.pages()[0]; + portfolioPage = new PortfolioPage(page); await portfolioPage.goto(); await portfolioPage.connectWallet(); - transactionsPage = await portfolioPage.viewTransactionsPage(); + transactionsPage = await new TransactionsPage(page).open(); }); test.afterAll(async () => { @@ -66,7 +68,7 @@ test.describe("Test Transactions feature", () => { await transactionsPage.closeTransaction(); }); - test("User should be able to see a new transaction", async () => { + test.skip("User should be able to see a new transaction", async () => { swapPage = new SwapPage(context.pages()[0]); await swapPage.goto(); await swapPage.selectPair("USDC", "USDT");