diff --git a/api/suggested-fees.ts b/api/suggested-fees.ts index 280ac2965..f15cc05fb 100644 --- a/api/suggested-fees.ts +++ b/api/suggested-fees.ts @@ -211,7 +211,9 @@ const handler = async ( .mul(parseUnits(tokenPriceUsd.toString(), 18)) .div(parseUnits("1", inputToken.decimals)); - if (amount.gt(maxDeposit)) { + const skipAmountLimitEnabled = skipAmountLimit === "true"; + + if (!skipAmountLimitEnabled && amount.gt(maxDeposit)) { throw new AmountTooHighError({ message: `Amount exceeds max. deposit limit: ${ethers.utils.formatUnits( maxDeposit, @@ -237,7 +239,6 @@ const handler = async ( const isAmountTooLow = BigNumber.from(amountInput).lt(minDeposit); - const skipAmountLimitEnabled = skipAmountLimit === "true"; if (!skipAmountLimitEnabled && isAmountTooLow) { throw new AmountTooLowError({ message: `Sent amount is too low relative to fees`, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 9ed9d6bfc..05a6c62b4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -529,3 +529,11 @@ export const vercelApiBaseUrl = export const defaultSwapSlippage = Number( process.env.REACT_APP_DEFAULT_SWAP_SLIPPAGE || 0.5 ); + +// List of addresses that have special manual rebalancing rights in the FE +export const manualRebalancerAddresses = String( + process.env.REACT_APP_MANUAL_REBALANCER_ADDRESSES || + "0x1d933Fd71FF07E69f066d50B39a7C34EB3b69F05" +) + .split(",") + .map(ethers.utils.getAddress); diff --git a/src/views/Bridge/hooks/useBridge.ts b/src/views/Bridge/hooks/useBridge.ts index 40d54ec3f..71ad05f34 100644 --- a/src/views/Bridge/hooks/useBridge.ts +++ b/src/views/Bridge/hooks/useBridge.ts @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; -import { BigNumber } from "ethers"; +import { BigNumber, utils, constants } from "ethers"; import { useConnection, useIsWrongNetwork, useAmplitude } from "hooks"; import { ampli } from "ampli"; -import { defaultSwapSlippage, bnZero } from "utils"; +import { defaultSwapSlippage, bnZero, manualRebalancerAddresses } from "utils"; import { useBridgeAction } from "./useBridgeAction"; import { useToAccount } from "./useToAccount"; @@ -58,12 +58,14 @@ export function useBridge() { shouldUpdateQuote && (transferQuoteQuery.isInitialLoading || feesQuery.isInitialLoading) && !transferQuote; + const isManualRebalancer = + account && manualRebalancerAddresses.includes(utils.getAddress(account)); const { error: amountValidationError } = validateBridgeAmount( parsedAmount, quotedFees?.isAmountTooLow, maxBalance, - limitsQuery.limits?.maxDeposit, + isManualRebalancer ? constants.MaxUint256 : limitsQuery.limits?.maxDeposit, selectedRoute.type === "swap" && quotedSwap?.minExpectedInputTokenAmount ? BigNumber.from(quotedSwap?.minExpectedInputTokenAmount) : parsedAmount diff --git a/src/views/Bridge/hooks/useBridgeAction.ts b/src/views/Bridge/hooks/useBridgeAction.ts index 868bd4bfb..9b100a097 100644 --- a/src/views/Bridge/hooks/useBridgeAction.ts +++ b/src/views/Bridge/hooks/useBridgeAction.ts @@ -3,7 +3,7 @@ import { TransferQuoteReceivedProperties, ampli, } from "ampli"; -import { BigNumber, constants, providers } from "ethers"; +import { BigNumber, constants, providers, utils } from "ethers"; import { useConnection, useApprove, @@ -23,6 +23,8 @@ import { sendSpokePoolVerifierDepositTx, sendDepositV3Tx, sendSwapAndBridgeTx, + manualRebalancerAddresses, + ChainId, } from "utils"; import { TransferQuote } from "./useTransferQuote"; import { SelectedRoute } from "../utils"; @@ -153,7 +155,32 @@ export function useBridgeAction( let tx: providers.TransactionResponse; - if (isSwapRoute) { + // If the connected wallet is configured as a manual rebalancer and the origin + // chain is mainnet, we force the deposit to be unprofitable with a long + // exclusivity. The idea is to have these deposits paid out as a slow fill, in + // which case the SpokePool's own balance will be used to complete the fill, and + // the HubPool will pull funds back from the mainnet SpokePool instead. This + // allows us to skip the 7 day bridges and ensure a safe level of HubPool liquidity + if ( + manualRebalancerAddresses.includes(utils.getAddress(frozenAccount)) && + frozenRoute.fromChain === ChainId.MAINNET + ) { + const { spokePool } = await getSpokePoolAndVerifier(frozenRoute); + tx = await sendDepositV3Tx( + signer, + { + ...frozenDepositArgs, + inputTokenAddress: frozenRoute.fromTokenAddress, + outputTokenAddress: frozenRoute.toTokenAddress, + // Force overrides to make deposit unattractive for relayers + relayerFeePct: BigNumber.from(0), + exclusiveRelayer: frozenAccount, + exclusivityDeadline: 10 * 60, // 10 minutes + }, + spokePool, + networkMismatchHandler + ); + } else if (isSwapRoute) { tx = await sendSwapAndBridgeTx( signer, {