diff --git a/package-lock.json b/package-lock.json index b3d4e84ed3..9ec2aaf6fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "@balancer/frontend-v2", - "version": "1.114.20", + "version": "1.114.28", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@balancer/frontend-v2", - "version": "1.114.20", + "version": "1.114.28", "license": "MIT", "devDependencies": { "@aave/protocol-js": "^4.3.0", "@balancer-labs/assets": "github:balancer-labs/assets#master", - "@balancer-labs/sdk": "^1.1.3-beta.13", + "@balancer-labs/sdk": "^1.1.3-beta.14", "@balancer-labs/typechain": "^1.0.0", "@balancer-labs/v2-deployments": "^3.2.0", "@cowprotocol/contracts": "^1.3.1", @@ -1522,9 +1522,9 @@ } }, "node_modules/@balancer-labs/sdk": { - "version": "1.1.3-beta.13", - "resolved": "https://registry.npmjs.org/@balancer-labs/sdk/-/sdk-1.1.3-beta.13.tgz", - "integrity": "sha512-G+zflKEjQZnNx0lSr9R4NeeN0vM6HiuFFWNwlrv6N8ZcF0LAVADWJE530oRpQFGxSGuQwTQscGrMSk4wJwpr7A==", + "version": "1.1.3-beta.14", + "resolved": "https://registry.npmjs.org/@balancer-labs/sdk/-/sdk-1.1.3-beta.14.tgz", + "integrity": "sha512-cVyImbrJZ4660Jj0RQmxTIIWQ4RTTCw0WK0O0eLd+jhfuda44VnCeeWvnYp/mTpbfcXLQzbapvvGMS8bnNuRWg==", "dev": true, "dependencies": { "@balancer-labs/sor": "4.1.1-beta.13", @@ -29529,9 +29529,9 @@ } }, "@balancer-labs/sdk": { - "version": "1.1.3-beta.13", - "resolved": "https://registry.npmjs.org/@balancer-labs/sdk/-/sdk-1.1.3-beta.13.tgz", - "integrity": "sha512-G+zflKEjQZnNx0lSr9R4NeeN0vM6HiuFFWNwlrv6N8ZcF0LAVADWJE530oRpQFGxSGuQwTQscGrMSk4wJwpr7A==", + "version": "1.1.3-beta.14", + "resolved": "https://registry.npmjs.org/@balancer-labs/sdk/-/sdk-1.1.3-beta.14.tgz", + "integrity": "sha512-cVyImbrJZ4660Jj0RQmxTIIWQ4RTTCw0WK0O0eLd+jhfuda44VnCeeWvnYp/mTpbfcXLQzbapvvGMS8bnNuRWg==", "dev": true, "requires": { "@balancer-labs/sor": "4.1.1-beta.13", diff --git a/package.json b/package.json index 8a0e42454d..38fd8c9d24 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@balancer/frontend-v2", - "version": "1.114.20", + "version": "1.114.28", "engines": { "node": "=16", "npm": ">=8" @@ -42,7 +42,7 @@ "devDependencies": { "@aave/protocol-js": "^4.3.0", "@balancer-labs/assets": "github:balancer-labs/assets#master", - "@balancer-labs/sdk": "^1.1.3-beta.13", + "@balancer-labs/sdk": "^1.1.3-beta.14", "@balancer-labs/typechain": "^1.0.0", "@balancer-labs/v2-deployments": "^3.2.0", "@cowprotocol/contracts": "^1.3.1", diff --git a/src/components/contextual/pages/claim/LegacyClaims.vue b/src/components/contextual/pages/claim/LegacyClaims.vue index 8819aecd56..d4903c48b4 100644 --- a/src/components/contextual/pages/claim/LegacyClaims.vue +++ b/src/components/contextual/pages/claim/LegacyClaims.vue @@ -139,7 +139,7 @@ async function claimAvailableRewards() { }); txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { isClaiming.value = false; userClaimsQuery.refetch(); }, diff --git a/src/components/contextual/pages/vebal/cross-chain-boost/SyncNetworkAction.vue b/src/components/contextual/pages/vebal/cross-chain-boost/SyncNetworkAction.vue index 16885acc78..0ed7f8d015 100644 --- a/src/components/contextual/pages/vebal/cross-chain-boost/SyncNetworkAction.vue +++ b/src/components/contextual/pages/vebal/cross-chain-boost/SyncNetworkAction.vue @@ -57,7 +57,7 @@ async function handleTransaction( }); txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { setSyncTxHashes(network, tx.hash); }, onTxFailed: () => { diff --git a/src/components/forms/pool_actions/WithdrawForm/WithdrawForm.vue b/src/components/forms/pool_actions/WithdrawForm/WithdrawForm.vue index 8a868db93d..b015b9d8c5 100644 --- a/src/components/forms/pool_actions/WithdrawForm/WithdrawForm.vue +++ b/src/components/forms/pool_actions/WithdrawForm/WithdrawForm.vue @@ -14,6 +14,7 @@ import { } from '@/composables/usePoolHelpers'; import { useI18n } from 'vue-i18n'; import { Pool } from '@/services/pool/types'; +import useNetwork from '@/composables/useNetwork'; type Props = { pool: Pool; @@ -37,7 +38,8 @@ const showPreview = ref(false); const { t } = useI18n(); const { veBalTokenInfo } = useVeBal(); const { wrappedNativeAsset, nativeAsset } = useTokens(); - +const router = useRouter(); +const { networkSlug } = useNetwork(); const { isWalletReady, startConnectWithInjectedProvider, isMismatchedNetwork } = useWeb3(); const { @@ -52,6 +54,7 @@ const { hasAcceptedHighPriceImpact, hasAmountsOut, validAmounts, + hasBpt, } = useExitPool(); const { isWrappedNativeAssetPool } = usePoolHelpers(pool); @@ -90,6 +93,10 @@ const excludedTokens = computed((): string[] => { * CALLBACKS */ onBeforeMount(() => { + // If user has no BPT when mounting this component, redirect back to pool page + if (!hasBpt.value) + router.push({ name: 'pool', params: { networkSlug, id: props.pool.id } }); + singleAmountOut.address = isPreMintedBptType(pool.value.poolType) ? wrappedNativeAsset.value.address : pool.value.tokensList[0]; diff --git a/src/components/forms/pool_actions/WithdrawForm/components/WithdrawPreviewModal/WithdrawPreviewModal.vue b/src/components/forms/pool_actions/WithdrawForm/components/WithdrawPreviewModal/WithdrawPreviewModal.vue index 405534c093..495c14757a 100644 --- a/src/components/forms/pool_actions/WithdrawForm/components/WithdrawPreviewModal/WithdrawPreviewModal.vue +++ b/src/components/forms/pool_actions/WithdrawForm/components/WithdrawPreviewModal/WithdrawPreviewModal.vue @@ -42,6 +42,7 @@ const withdrawalConfirmed = ref(false); const { t } = useI18n(); const { getToken } = useTokens(); const { networkSlug } = useNetwork(); +const router = useRouter(); const { bptIn, @@ -52,6 +53,7 @@ const { fiatAmountsOut, isSingleAssetExit, shouldExitViaInternalBalance, + hasBpt, } = useExitPool(); /** @@ -106,7 +108,13 @@ const amountsOutMap = computed((): AmountMap => { * METHODS */ function handleClose(): void { - emit('close'); + // If user has withdrawn everything, send back to pool page. Else, close + // modal. + if (!hasBpt.value) { + router.push({ name: 'pool', params: { networkSlug, id: props.pool.id } }); + } else { + emit('close'); + } } diff --git a/src/composables/approvals/useRelayerApprovalTx.ts b/src/composables/approvals/useRelayerApprovalTx.ts index 5b675537df..8ac8c13b96 100644 --- a/src/composables/approvals/useRelayerApprovalTx.ts +++ b/src/composables/approvals/useRelayerApprovalTx.ts @@ -100,7 +100,7 @@ export default function useRelayerApprovalTx( }); approved.value = await txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { approving.value = false; relayerApproval.refetch(); }, diff --git a/src/composables/approvals/useTokenApproval.ts b/src/composables/approvals/useTokenApproval.ts index 8bb6054fd4..c059285d16 100644 --- a/src/composables/approvals/useTokenApproval.ts +++ b/src/composables/approvals/useTokenApproval.ts @@ -103,7 +103,7 @@ export default function useTokenApproval( }); txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { approving.value = false; approved.value = true; }, diff --git a/src/composables/swap/useJoinExit.ts b/src/composables/swap/useJoinExit.ts index e111c475f1..15547682ea 100644 --- a/src/composables/swap/useJoinExit.ts +++ b/src/composables/swap/useJoinExit.ts @@ -226,7 +226,7 @@ export default function useJoinExit({ } await txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { confirming.value = false; relayerApprovalQuery.refetch(); }, diff --git a/src/composables/swap/useSor.ts b/src/composables/swap/useSor.ts index 64fe720b0a..44ad9325b7 100644 --- a/src/composables/swap/useSor.ts +++ b/src/composables/swap/useSor.ts @@ -369,8 +369,6 @@ export default function useSor({ return; } - amount = bnum(amount).toString(); - const tokenInAddress = tokenInAddressInput.value; const tokenOutAddress = tokenOutAddressInput.value; @@ -591,7 +589,7 @@ export default function useSor({ toFiat(tokenInAmountInput.value, tokenInAddressInput.value) || '0'; txListener(tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { trackGoal(Goals.Swapped, bnum(swapUSDValue).times(100).toNumber() || 0); swapping.value = false; latestTxHash.value = tx.hash; diff --git a/src/composables/useEthers.ts b/src/composables/useEthers.ts index d48f816a7b..f43666518a 100644 --- a/src/composables/useEthers.ts +++ b/src/composables/useEthers.ts @@ -11,13 +11,12 @@ import { } from '@/lib/utils/promise'; import { rpcProviderService } from '@/services/rpc-provider/rpc-provider.service'; -import useBlocknative from './useBlocknative'; import { toJsTimestamp } from './useTime'; import { useTokens } from '@/providers/tokens.provider'; import useTransactions from './useTransactions'; import { captureBalancerException } from '@/lib/utils/errors'; -type ConfirmedTxCallback = (receipt: TransactionReceipt) => void; +type ConfirmedTxCallback = (receipt: TransactionReceipt) => Promise; type FailedTxCallback = (txData: TransactionResponse) => void; // keep a record of processed txs @@ -25,7 +24,6 @@ export const processedTxs = ref>(new Set('')); export default function useEthers() { const { finalizeTransaction, updateTransaction } = useTransactions(); - const { supportsBlocknative } = useBlocknative(); const { refetchBalances } = useTokens(); async function getTxConfirmedAt(receipt: TransactionReceipt): Promise { @@ -50,7 +48,7 @@ export default function useEthers() { txListener( tx, { - onTxConfirmed: () => { + onTxConfirmed: async () => { resolve(true); }, onTxFailed: () => { @@ -106,9 +104,9 @@ export default function useEthers() { if (receipt != null) { finalizeTransaction(txHash, 'tx', receipt); } - callbacks.onTxConfirmed(receipt); - if (shouldRefetchBalances && !supportsBlocknative.value) { - refetchBalances(); + await callbacks.onTxConfirmed(receipt); + if (shouldRefetchBalances) { + await refetchBalances(); } confirmed = true; } catch (error) { diff --git a/src/lib/config/arbitrum/pools.ts b/src/lib/config/arbitrum/pools.ts index f3b13a2cb2..9d8f842114 100644 --- a/src/lib/config/arbitrum/pools.ts +++ b/src/lib/config/arbitrum/pools.ts @@ -49,6 +49,7 @@ const pools: Pools = { '0xbe0f30217be1e981add883848d0773a86d2d2cd4000000000000000000000471', // rETH-bb-a-WETH '0x45c4d1376943ab28802b995acffc04903eb5223f000000000000000000000470', // wstETH-bb-a-WETH '0xc6eee8cb7643ec2f05f46d569e9ec8ef8b41b389000000000000000000000475', // bb-a-USD + '0x3fd4954a851ead144c2ff72b1f5a38ea5976bd54000000000000000000000480', // ankreth/wsteth ], }, Investment: { diff --git a/src/lib/config/mainnet/pools.ts b/src/lib/config/mainnet/pools.ts index 839b8dc086..78125dd315 100644 --- a/src/lib/config/mainnet/pools.ts +++ b/src/lib/config/mainnet/pools.ts @@ -113,6 +113,7 @@ const pools: Pools = { '0x3fa8c89704e5d07565444009e5d9e624b40be813000000000000000000000599', // gho/lusd '0x9a172e1cb0e99f7e6dcc4c52e4655e8f337d5c0000000000000000000000059a', // gho/mai '0xc2b021133d1b0cf07dba696fd5dd89338428225b000000000000000000000598', // gho/bb-a-usd + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7', // dusd-bb-a-usd ], }, Investment: { @@ -505,6 +506,12 @@ const pools: Pools = { boosted: true, boostedProtocols: [BoostedProtocol.Aave], }, + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7': { + name: 'DUSD/Boosted Aave v3 USD', + hasIcon: false, + boosted: true, + boostedProtocols: [BoostedProtocol.Aave], + }, }, Deep: [ '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', // bb-a-USD1 (mainnet) @@ -548,6 +555,7 @@ const pools: Pools = { '0x41503c9d499ddbd1dcdf818a1b05e9774203bf46000000000000000000000594', // wstETH-bb-a-WETH-BPT '0xd7edb56f63b2a0191742aea32df1f98ca81ed9c600000000000000000000058e', // B-wstETH/bb-ma3-weth '0xc2b021133d1b0cf07dba696fd5dd89338428225b000000000000000000000598', // gho/bb-a-usd + '0xe2d16b0a39f3fbb4389a0e8f1efcbecfb3d1e6e10000000000000000000005a7', // dusd-bb-a-usd ], BoostedApr: [ '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb2', // bb-a-USD1 (mainnet) diff --git a/src/lib/config/polygon/pools.ts b/src/lib/config/polygon/pools.ts index f85587c8d5..eb0984b04e 100644 --- a/src/lib/config/polygon/pools.ts +++ b/src/lib/config/polygon/pools.ts @@ -129,6 +129,7 @@ const pools: Pools = { '0x2814a9f16d7b5b5826df47f702f16279ccd799c8000200000000000000000bd1', // 50wMatic-50stMatic '0xa874e67a1b203819bce84f161eba4b4eb3f4359b000200000000000000000bcc', // 50USDC-50TRYB '0xa5a935833f6a5312715f182733eab088452335d7000100000000000000000bee', // 30WBTC-20stMATIC-bb-a-WMATIC + '0xaaf737aeb1e5f1dc9429de4a639fe16c42fa1fe3000200000000000000000bf9', // 50fireEP-50FBX ], }, Factories: { diff --git a/src/lib/utils/errors.spec.ts b/src/lib/utils/errors.spec.ts index c1598bb7eb..879c5a980e 100644 --- a/src/lib/utils/errors.spec.ts +++ b/src/lib/utils/errors.spec.ts @@ -22,6 +22,11 @@ describe('useTransactionErrors', () => { expect(isUserError(new Error('Transaction was rejected'))).toBe(true); }); + it('Should return true for common bad wallet config issues', () => { + expect(isUserError(new Error('invalid rpc url'))).toBe(true); + expect(isUserError(new Error('nonce has already been used'))).toBe(true); + }); + it('Should return true if the error message is in the cause', () => { const rejectionError = new Error('Something went wrong'); rejectionError.cause = new Error('User rejected the transaction'); @@ -50,6 +55,15 @@ describe('useTransactionErrors', () => { expect(isUserError(rejectionError)).toBe(true); }); + // See https://balancer-labs.sentry.io/issues/4327549689/events/0c8872a3076a4843bd7b836474160a44/ + it('Should return true if the not enough gas error is in russian', () => { + const rejectionError = { + message: + 'Комиссия за газ обновлена, и вам необходимо больше 0.00007039 ETH для завершения этой транзакции.', + }; + expect(isUserError(rejectionError)).toBe(true); + }); + // See https://balancer-labs.sentry.io/issues/4199718124/events/57d26b71647046f2be3620f3c0165714/ it('Should return true if its a user error as an object', () => { const rejectionError = { @@ -58,5 +72,14 @@ describe('useTransactionErrors', () => { }; expect(isUserError(rejectionError)).toBe(true); }); + + // See https://balancer-labs.sentry.io/issues/4327541491/events/9f490b42607646eb9065ebdd630226d2/ + it('Should return true if the cowswap fee has changed since the user received their quote', () => { + const rejectionError = { + message: + "Error: The signed fee is insufficient. It's possible that is higher now due to a change in the gas price, ether price, or the sell token price. Please try again to get an updated fee quote.", + }; + expect(isUserError(rejectionError)).toBe(true); + }); }); }); diff --git a/src/lib/utils/errors.ts b/src/lib/utils/errors.ts index 768ebe51bb..c1329cc1a2 100644 --- a/src/lib/utils/errors.ts +++ b/src/lib/utils/errors.ts @@ -257,10 +257,25 @@ function isUserRejected(error): boolean { } /** - * Checks if error is caused by user not having enough gas. + * Checks if error is caused by user not having enough gas or setting gas too low. */ function isUserNotEnoughGas(error): boolean { - const messages = [/insufficient funds for gas/]; + const messages = [ + /insufficient funds for gas/, + /the signed fee is insufficient/, + /EffectivePriorityFeePerGas too low/, + /Комиссия за газ обновлена/i, + /insufficient eth to pay the network fees/, + ]; + + return isErrorOfType(error, messages); +} + +/** + * Checks if error is caused by user's wallet having bad config / state + */ +function isWalletConfigError(error): boolean { + const messages = [/invalid rpc url/, /nonce has already been used/]; return isErrorOfType(error, messages); } @@ -289,7 +304,11 @@ function isBotError(error): boolean { * Checks if error is caused by the user or the state of their wallet. */ export function isUserError(error): boolean { - return isUserRejected(error) || isUserNotEnoughGas(error); + return ( + isUserRejected(error) || + isUserNotEnoughGas(error) || + isWalletConfigError(error) + ); } /** diff --git a/src/pages/pool/withdraw.vue b/src/pages/pool/withdraw.vue index 3b9e343365..8ca956e6c4 100644 --- a/src/pages/pool/withdraw.vue +++ b/src/pages/pool/withdraw.vue @@ -2,7 +2,6 @@ import { usePoolHelpers } from '@/composables/usePoolHelpers'; import { oneSecondInMs } from '@/composables/useTime'; import { useIntervalFn } from '@vueuse/core'; -import { computed } from 'vue'; import { hasFetchedPoolsForSor } from '@/lib/balancer.sdk'; import WithdrawPage from '@/components/contextual/pages/pool/withdraw/WithdrawPage.vue'; import { useTokens } from '@/providers/tokens.provider';