@ethersproject/bytes#hexDataSlice TypeScript Examples

The following examples show how to use @ethersproject/bytes#hexDataSlice. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: hdwallet.ts    From fuels-ts with Apache License 2.0 6 votes vote down vote up
/**
   * HDWallet is a implementation of the BIP-0044 and BIP-0032, Multi-Account Hierarchy for Deterministic Wallets
   *
   * @param config - Wallet configurations
   */
  constructor(config: HDWalletConfig) {
    // TODO: set some asserts here

    if (config.privateKey) {
      const signer = new Signer(config.privateKey);
      this.publicKey = hexlify(signer.compressedPublicKey);
      this.privateKey = hexlify(config.privateKey);
    } else {
      if (!config.publicKey) {
        throw new Error('Public and Private Key are missing!');
      }
      this.publicKey = hexlify(config.publicKey);
    }

    this.parentFingerprint = config.parentFingerprint || this.parentFingerprint;
    this.fingerprint = hexDataSlice(ripemd160(sha256(this.publicKey)), 0, 4);
    this.depth = config.depth || this.depth;
    this.index = config.index || this.index;
    this.chainCode = config.chainCode;
  }
Example #2
Source File: mnemonic.ts    From fuels-ts with Apache License 2.0 6 votes vote down vote up
/**
   * Get the extendKey as defined on BIP-32 from the provided seed
   *
   * @param seed - BIP39 seed
   * @param testnet - Inform if should use testnet or mainnet prefix, default value is true (`mainnet`).
   * @returns BIP-32 extended private key
   */
  static seedToExtendedKey(seed: string, testnet: boolean = false): string {
    const masterKey = Mnemonic.masterKeysFromSeed(seed);
    const prefix = arrayify(testnet ? TestnetPRV : MainnetPRV);
    const depth = '0x00';
    const fingerprint = '0x00000000';
    const index = '0x00000000';
    // last 32 bites from the key
    const chainCode = masterKey.slice(32);
    // first 32 bites from the key
    const privateKey = masterKey.slice(0, 32);
    const extendedKey = concat([
      prefix,
      depth,
      fingerprint,
      index,
      chainCode,
      concat(['0x00', privateKey]),
    ]);
    const checksum = hexDataSlice(sha256(sha256(extendedKey)), 0, 4);

    return Base58.encode(concat([extendedKey, checksum]));
  }
Example #3
Source File: permits.ts    From hypertext with GNU General Public License v3.0 6 votes vote down vote up
DAIPermitGatherer: PermitGathererFunction = async (address, deadline, _, library) => {
  const Permit = [
    { name: 'holder', type: 'address' },
    { name: 'spender', type: 'address' },
    { name: 'nonce', type: 'uint256' },
    { name: 'expiry', type: 'uint256' },
    { name: 'allowed', type: 'bool' },
  ]
  const domain = {
    name: 'Dai Stablecoin',
    version: '1',
  }
  const DAIContract = new Contract(
    DAI.address,
    ['function nonces(address holder) view returns (uint256 nonce)'],
    library
  )
  const nonce: BigNumber = await DAIContract.nonces(address)
  const message = {
    holder: address,
    spender: PERMIT_AND_CALL_ADDRESS,
    nonce: await Promise.resolve(nonce.toNumber()).catch(() => nonce.toString()),
    expiry: deadline,
    allowed: true, // DAI only allows unlimited approves
  }
  const inputs = ['address', 'address', 'uint256', 'uint256', 'bool', 'uint8', 'bytes32', 'bytes32']
  return {
    types: {
      EIP712Domain,
      Permit,
    },
    domain,
    message,
    permitSelector: hexDataSlice(id(`permit(${inputs.join(',')})`), 0, 4),
    getPermitData: ({ v, r, s }) =>
      defaultAbiCoder.encode(inputs, [address, PERMIT_AND_CALL_ADDRESS, nonce, deadline, true, v, r, s]),
  }
}
Example #4
Source File: permits.ts    From hypertext with GNU General Public License v3.0 6 votes vote down vote up
USDCPermitGatherer: PermitGathererFunction = async (address, deadline, approveAmount, library) => {
  const Permit = [
    { name: 'owner', type: 'address' },
    { name: 'spender', type: 'address' },
    { name: 'value', type: 'uint256' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
  ]
  const domain = {
    name: 'USD Coin',
    version: '2',
  }
  const USDCContract = new Contract(
    USDC.address,
    ['function nonces(address owner) view returns (uint256 nonce)'],
    library
  )
  const nonce: BigNumber = await USDCContract.nonces(address)
  const value = `0x${approveAmount.toString(16)}`
  const message = {
    owner: address,
    spender: PERMIT_AND_CALL_ADDRESS,
    value,
    nonce: await Promise.resolve(nonce.toNumber()).catch(() => nonce.toString()),
    deadline,
  }
  const inputs = ['address', 'address', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32']
  return {
    types: {
      EIP712Domain,
      Permit,
    },
    domain,
    message,
    permitSelector: hexDataSlice(id(`permit(${inputs.join(',')})`), 0, 4),
    getPermitData: ({ v, r, s }) =>
      defaultAbiCoder.encode(inputs, [address, PERMIT_AND_CALL_ADDRESS, value, deadline, v, r, s]),
  }
}
Example #5
Source File: permits.ts    From hypertext with GNU General Public License v3.0 6 votes vote down vote up
UNIPermitGatherer: PermitGathererFunction = async (address, deadline, approveAmount, library) => {
  const Permit = [
    { name: 'owner', type: 'address' },
    { name: 'spender', type: 'address' },
    { name: 'value', type: 'uint256' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
  ]
  const domain = { name: 'Uniswap' }
  const UNIContract = new Contract(
    UNI.address,
    ['function nonces(address holder) view returns (uint256 nonce)'],
    library
  )
  const nonce: BigNumber = await UNIContract.nonces(address)
  const value = `0x${approveAmount.toString(16)}`
  const message = {
    owner: address,
    spender: PERMIT_AND_CALL_ADDRESS,
    value,
    nonce: await Promise.resolve(nonce.toNumber()).catch(() => nonce.toString()),
    deadline,
  }
  const inputs = ['address', 'address', 'uint256', 'uint256', 'uint8', 'bytes32', 'bytes32']
  return {
    types: {
      EIP712Domain: EIP712DomainWithoutVersion,
      Permit,
    },
    domain,
    message,
    permitSelector: hexDataSlice(id(`permit(${inputs.join(',')})`), 0, 4),
    getPermitData: ({ v, r, s }) =>
      defaultAbiCoder.encode(inputs, [address, PERMIT_AND_CALL_ADDRESS, value, deadline, v, r, s]),
  }
}
Example #6
Source File: logs.ts    From solana-solidity.js with Apache License 2.0 6 votes vote down vote up
/** @internal */
export function parseTransactionError(
    encoded: Buffer | null,
    computeUnitsUsed: number,
    log: string | null,
    logs: string[]
): TransactionError {
    let error: TransactionError;

    if (log) {
        error = new TransactionError(log);
    } else if (!encoded) {
        const failedMatch = logs[logs.length - 1].match(LOG_FAILED_REGEX);
        error = failedMatch ? new TransactionError(failedMatch[2]) : new TransactionError('return data or log not set');
    } else if (encoded.readUInt32BE(0) != 0x08c379a0) {
        error = new TransactionError('signature not correct');
    } else {
        const revertReason = defaultAbiCoder.decode(['string'], hexDataSlice(encoded, 4));
        error = new TransactionError(revertReason.toString());
    }

    error.logs = logs;
    error.computeUnitsUsed = computeUnitsUsed;

    return error;
}
Example #7
Source File: hdwallet.ts    From fuels-ts with Apache License 2.0 5 votes vote down vote up
function base58check(data: Uint8Array): string {
  return Base58.encode(concat([data, hexDataSlice(sha256(sha256(data)), 0, 4)]));
}
Example #8
Source File: Swap.tsx    From hypertext with GNU General Public License v3.0 4 votes vote down vote up
export default function Swap({ buy }: { buy: boolean }): JSX.Element {
  const { query, pathname, replace } = useRouter()

  const queryParameters = useQueryParameters()

  const { account, chainId, library } = useWeb3React()

  const [approveMax] = useApproveMax()
  const [deadlineDelta] = useDeadline()
  const [slippage] = useSlippage()
  const [, { addTransaction }] = useTransactions()

  // reducer state
  const [state, dispatch] = useReducer(
    reducer,
    {
      independentField: buy ? Field.OUTPUT : Field.INPUT,
      [Field.INPUT]: {
        address: queryParameters[QueryParameters.INPUT],
      },
      [Field.OUTPUT]: {
        address: queryParameters[QueryParameters.OUTPUT],
      },
    },
    initializeSentenceState
  )
  const { independentField, value, ...tokenAddresses } = state

  // derived state
  const dependentField = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
  const tradeType = independentField === Field.INPUT ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT

  // sdk tokens
  const tokens = {
    [Field.INPUT]: useTokenByAddressAndAutomaticallyAdd(tokenAddresses[Field.INPUT].address),
    [Field.OUTPUT]: useTokenByAddressAndAutomaticallyAdd(tokenAddresses[Field.OUTPUT].address),
  }

  // keep global token state in sync
  const [, setFirstToken] = useFirstToken()
  const [, setSecondToken] = useSecondToken()
  useEffect(() => {
    setFirstToken(tokens[buy ? Field.OUTPUT : Field.INPUT])
    setSecondToken(tokens[buy ? Field.INPUT : Field.OUTPUT])
  })

  // sdk route
  const [naiveRoute, allPairs] = useRoute(tokens[Field.INPUT], tokens[Field.OUTPUT])

  // parse user value
  const parsed: { [field: number]: TokenAmount } = {}
  if (value !== '' && value !== '.' && tokens[independentField]) {
    try {
      const valueParsed = parseUnits(value, tokens[independentField]?.decimals).toString()
      if (valueParsed !== '0') {
        parsed[independentField] = new TokenAmount(tokens[independentField] as Token, valueParsed)
      }
    } catch {
      // should only fail if the user specifies too many decimal places of precision (or maybe exceed max uint?)
    }
  }

  // sdk trade
  const trade = useTrade(tokens[Field.INPUT], tokens[Field.OUTPUT], allPairs, parsed[independentField], tradeType)

  const route = trade ? trade.route : naiveRoute

  // populate the parsed dependent field
  if (trade) {
    if (tradeType === TradeType.EXACT_INPUT) {
      parsed[dependentField] = trade.minimumAmountOut(new Percent(`${slippage}`, `${10000}`))
    } else {
      parsed[dependentField] = trade.maximumAmountIn(new Percent(`${slippage}`, `${10000}`))
    }
  }

  // usd values
  const [showUSD] = useShowUSD()
  const USDPrices = {
    [Field.INPUT]: useUSDTokenPrice(tokens[Field.INPUT]),
    [Field.OUTPUT]: useUSDTokenPrice(tokens[Field.OUTPUT]),
  }
  const USDAmountsFormatted = {
    [Field.INPUT]:
      parsed[Field.INPUT] && USDPrices[Field.INPUT]
        ? parsed[Field.INPUT].multiply(USDPrices[Field.INPUT] as Fraction).toFixed(2, { groupSeparator: ',' })
        : undefined,
    [Field.OUTPUT]:
      parsed[Field.OUTPUT] && USDPrices[Field.OUTPUT]
        ? parsed[Field.OUTPUT].multiply(USDPrices[Field.OUTPUT] as Fraction).toFixed(2, { groupSeparator: ',' })
        : undefined,
  }

  // calculate the formatted values from the parsed
  const formatted = {
    [independentField]: value,
    [dependentField]: parsed[dependentField] ? parsed[dependentField].toSignificant(4, { groupSeparator: ',' }) : '',
  }

  // reset when the network changes
  useEffect(() => {
    if (typeof chainId === 'number') {
      return (): void => {
        dispatch({
          type: ActionType.RESET,
          payload: { field: buy ? Field.OUTPUT : Field.INPUT },
        })
      }
    }
  }, [chainId, buy])

  // clear url params
  useEffect(() => {
    if (Object.keys(query).length > 0) {
      if (isIPFS) {
        window.history.replaceState(null, '', `.${pathname}.html`)
      } else {
        replace(pathname, undefined, { shallow: true })
      }
    }
  })

  // get input allowance for validation purposes
  const { data: _allowance } = useTokenAllowance(tokens[Field.INPUT], account, ROUTER_ADDRESS)
  const allowance = tokens[Field.INPUT]?.equals(WETH[(tokens[Field.INPUT] as Token).chainId])
    ? new TokenAmount(WETH[(tokens[Field.INPUT] as Token).chainId], MAX_UINT256)
    : _allowance

  // get permitAndCall allowance if the input token supports permit
  const { data: permitAndCallAllowance } = useTokenAllowance(
    canPermit(tokens[Field.INPUT]) ? tokens[Field.INPUT] : undefined,
    account,
    PERMIT_AND_CALL_ADDRESS
  )

  // get input balance for validation purposes
  const ETHBalance = useETHBalance(account)
  const _balance = useTokenBalance(tokens[Field.INPUT], account)
  const balance = tokens[Field.INPUT]?.equals(WETH[(tokens[Field.INPUT] as Token)?.chainId])
    ? ETHBalance.data
    : _balance.data

  // compute flags for warning states
  const warning = !!trade && Number.parseFloat(trade.slippage.toSignificant(2)) >= 5
  const danger = !!trade && Number.parseFloat(trade.slippage.toSignificant(2)) >= 10

  // compute validation flags
  const isInvalidBalance =
    parsed[Field.INPUT] && balance ? JSBI.greaterThan(parsed[Field.INPUT].raw, balance.raw) : false
  const isInvalidRoute = route === null && value.length > 0
  const isInvalidTrade = route && parsed[independentField] ? !!!trade : false

  // compute flag for whether maxing is allowed
  const canMax =
    !tokens[Field.INPUT]?.equals(WETH[(tokens[Field.INPUT] as Token).chainId]) &&
    !isInvalidRoute &&
    formatted[Field.INPUT]?.length === 0 &&
    !!balance &&
    JSBI.greaterThan(balance.raw, ZERO)

  // function to perform the swap
  const [swapping, setSwapping] = useState(false)
  const inputToken = useContract(tokens[Field.INPUT]?.address, IERC20.abi, true)
  const router = useContract(ROUTER_ADDRESS, IUniswapV2Router02ABI, true)
  async function swap(): Promise<void> {
    setSwapping(true)

    async function innerSwap(deadline: number, mockGas = false, permit?: Permit): Promise<{ hash: string }> {
      let routerFunctionNames: string[]
      let routerArguments: any[] // eslint-disable-line @typescript-eslint/no-explicit-any
      let value: Required<PayableOverrides>['value'] = 0

      if (trade?.tradeType === TradeType.EXACT_INPUT) {
        if ((tokens[Field.INPUT] as Token).equals(WETH[(tokens[Field.INPUT] as Token).chainId])) {
          routerFunctionNames = ['swapExactETHForTokens', 'swapExactETHForTokensSupportingFeeOnTransferTokens']
          routerArguments = [
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
          value = `0x${parsed[Field.INPUT].raw.toString(16)}`
        } else if ((tokens[Field.OUTPUT] as Token).equals(WETH[(tokens[Field.OUTPUT] as Token).chainId])) {
          routerFunctionNames = ['swapExactTokensForETH', 'swapExactTokensForETHSupportingFeeOnTransferTokens']
          routerArguments = [
            `0x${parsed[Field.INPUT].raw.toString(16)}`,
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
        } else {
          routerFunctionNames = ['swapExactTokensForTokens', 'swapExactTokensForTokensSupportingFeeOnTransferTokens']
          routerArguments = [
            `0x${parsed[Field.INPUT].raw.toString(16)}`,
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
        }
      } else {
        if ((tokens[Field.INPUT] as Token).equals(WETH[(tokens[Field.INPUT] as Token).chainId])) {
          routerFunctionNames = ['swapETHForExactTokens']
          routerArguments = [
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
          value = `0x${parsed[Field.INPUT].raw.toString(16)}`
        } else if ((tokens[Field.OUTPUT] as Token).equals(WETH[(tokens[Field.OUTPUT] as Token).chainId])) {
          routerFunctionNames = ['swapTokensForExactETH']
          routerArguments = [
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            `0x${parsed[Field.INPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
        } else {
          routerFunctionNames = ['swapTokensForExactTokens']
          routerArguments = [
            `0x${parsed[Field.OUTPUT].raw.toString(16)}`,
            `0x${parsed[Field.INPUT].raw.toString(16)}`,
            (route as Route).path.map((token) => token.address),
            account,
            deadline,
          ]
        }
      }

      // we have an approve tx pending
      if (mockGas) {
        // because we can't estimate gas, as it will fail b/c of the approve, we are forced to use the first function
        const routerFunctionName = routerFunctionNames[0]
        return await (router as Contract)
          [routerFunctionName](...routerArguments, {
            value,
            gasLimit: GAS_LIMIT_WHEN_MOCKING,
          })
          .catch((error: ErrorWithCode) => {
            if (error?.code !== 4001) {
              console.log(`${routerFunctionName} failed with a mocked gas limit.`, error)
            }
            throw error
          })
      }

      // we have permit data
      if (permit) {
        const permitAndCall = new Contract(
          PERMIT_AND_CALL_ADDRESS,
          [
            'function permitAndCall(address token, uint256 value, bytes4 permitSelector, bytes calldata permitData, bytes4 routerFunctionSelector, bytes calldata routerFunctionData)',
          ],
          library.getSigner(account).connectUnchecked()
        )

        // try to get a gas limit for each function name in turn
        for (const routerFunctionName of routerFunctionNames) {
          const routerFunctionFragment = (router as Contract).interface.fragments.filter(
            ({ name }) => name === routerFunctionName
          )[0]
          const routerFunctionSelector = hexDataSlice(
            id(`${routerFunctionName}(${routerFunctionFragment?.inputs.map(({ type }) => type).join(',')})`),
            0,
            4
          )
          const permitAndCallArguments = [
            (tokens[Field.INPUT] as Token).address,
            `0x${parsed[Field.INPUT].raw.toString(16)}`,
            permit.permitSelector,
            permit.permitData,
            routerFunctionSelector,
            defaultAbiCoder.encode(routerFunctionFragment.inputs, routerArguments),
          ]
          const gasLimit: BigNumber | void = await permitAndCall.estimateGas
            .permitAndCall(...permitAndCallArguments, { value })
            .then((gasLimit) => gasLimit.mul(105).div(100))
            .catch((error) => {
              console.log(`estimateGas failed for ${routerFunctionName} via permitAndCall.`, error)
            })
          if (BigNumber.isBigNumber(gasLimit)) {
            return await permitAndCall
              .permitAndCall(...permitAndCallArguments, {
                value,
                gasLimit,
              })
              .catch((error: ErrorWithCode) => {
                if (error?.code !== 4001) {
                  console.log(`${routerFunctionName} failed via permitAndCall.`, error)
                }
                throw error
              })
          }
        }
        // if we're here, it means all estimateGas calls failed
        console.log(
          routerFunctionNames.length === 1
            ? "If you're trying to swap a token that takes a transfer fee, you must specify an exact input amount."
            : "If you're trying to swap a token that takes a transfer fee, ensure your slippage tolerance is higher than the fee."
        )
        throw Error()
      }

      // try to get a gas limit for each function name in turn
      for (const routerFunctionName of routerFunctionNames) {
        const gasLimit: BigNumber | void = await (router as Contract).estimateGas[
          routerFunctionName
        ](...routerArguments, { value })
          .then((gasLimit) => gasLimit.mul(105).div(100))
          .catch((error) => {
            console.log(`estimateGas failed for ${routerFunctionName}.`, error)
          })
        if (BigNumber.isBigNumber(gasLimit)) {
          return await (router as Contract)
            [routerFunctionName](...routerArguments, { value, gasLimit })
            .catch((error: ErrorWithCode) => {
              if (error?.code !== 4001) {
                console.log(`${routerFunctionName} failed.`, error)
              }
              throw error
            })
        }
      }
      // if we're here, it means all estimateGas calls failed
      console.log(
        routerFunctionNames.length === 1
          ? "If you're trying to swap a token that takes a transfer fee, you must specify an exact input amount."
          : "If you're trying to swap a token that takes a transfer fee, ensure your slippage tolerance is higher than the fee."
      )
      throw Error()
    }

    const deadline = Math.floor(Date.now() / 1000) + deadlineDelta
    let approved = JSBI.greaterThanOrEqual((allowance as TokenAmount).raw, parsed[Field.INPUT].raw)
    let mockGas = false
    let permit: Permit | undefined
    if (!approved) {
      let tryToManuallyApprove = true

      // attempt to gather a permit signature where possible
      if (canPermit(tokens[Field.INPUT])) {
        // in the slightly weird case where the user has already approved PermitAndCall, just fake the permit
        if (permitAndCallAllowance && JSBI.greaterThanOrEqual(permitAndCallAllowance.raw, parsed[Field.INPUT].raw)) {
          approved = true
          tryToManuallyApprove = false
          permit = {
            permitSelector: '0x00000000',
            permitData: '0x',
          }
        } else {
          await gatherPermit(
            account as string,
            deadline,
            approveMax ? MAX_UINT256 : parsed[Field.INPUT].raw,
            tokens[Field.INPUT] as Token,
            library
          )
            .then((gatheredPermit) => {
              approved = true
              tryToManuallyApprove = false
              permit = gatheredPermit
            })
            .catch((error) => {
              // if the error code is 4001 (EIP-1193 user rejected request), we don't want to try a manual approve
              if (error?.code === 4001) {
                tryToManuallyApprove = false
              } else {
                console.log(`permit failed.`, error)
              }
            })
        }
      }

      if (tryToManuallyApprove) {
        await (inputToken as Contract)
          .approve(ROUTER_ADDRESS, `0x${(approveMax ? MAX_UINT256 : parsed[Field.INPUT].raw).toString(16)}`)
          .then(({ hash }: { hash: string }) => {
            addTransaction(chainId as number, hash)
            approved = true
            mockGas = true
          })
          .catch((error: ErrorWithCode) => {
            if (error?.code !== 4001) {
              console.log(`approve failed.`, error)
            }
          })
      }
    }

    if (approved) {
      return (
        innerSwap(deadline, mockGas, permit)
          .then(({ hash }) => {
            addTransaction(chainId as number, hash)
            dispatch({
              type: ActionType.TYPE,
              payload: { field: independentField, value: '' },
            })
            setSwapping(false)
          })
          // we don't do anything with the error here, innerSwap is responsible for handling it
          .catch(() => {
            setSwapping(false)
          })
      )
    } else {
      setSwapping(false)
    }
  }

  const formattedQueryParams = formatQueryParams({
    ...(tokens[Field.INPUT]?.address ? { [QueryParameters.INPUT]: (tokens[Field.INPUT] as Token).address } : {}),
    ...(tokens[Field.OUTPUT]?.address ? { [QueryParameters.OUTPUT]: (tokens[Field.OUTPUT] as Token).address } : {}),
  })

  return (
    <Stack
      direction="column"
      align="center"
      spacing="6rem"
      flexGrow={1}
      justifyContent="center"
      px="2.5rem"
      py="8rem"
      shouldWrapChildren
    >
      <Stack direction="row" align="flex-start" spacing="1rem" flexWrap="wrap" shouldWrapChildren>
        <SwapText>I want to</SwapText>

        {!!!trade ? (
          isIPFS ? (
            <Button
              as="a"
              {...{
                href: `./${buy ? 'sell' : 'buy'}.html${formattedQueryParams}`,
              }}
              variant="ghost"
              variantColor={buy ? 'green' : 'red'}
              p="0.5rem"
              mt="-0.2rem"
              fontSize="3xl"
              lineHeight={1}
              height="min-content"
            >
              {buy ? 'Buy' : 'Sell'}
            </Button>
          ) : (
            <NextLink href={`/${buy ? 'sell' : 'buy'}${formattedQueryParams}`} passHref>
              <Button
                as="a"
                variant="ghost"
                variantColor={buy ? 'green' : 'red'}
                p="0.5rem"
                mt="-0.2rem"
                fontSize="3xl"
                lineHeight={1}
                height="min-content"
              >
                {buy ? 'Buy' : 'Sell'}
              </Button>
            </NextLink>
          )
        ) : (
          <Button
            variant="solid"
            variantColor={!warning ? (buy ? 'green' : 'red') : 'yellow'}
            p="0.75rem"
            mt="-0.45rem"
            fontSize="3xl"
            lineHeight={1}
            height="min-content"
            leftIcon={!warning ? undefined : !danger ? 'warning-2' : 'not-allowed'}
            isDisabled={!account || isInvalidBalance || isInvalidTrade}
            isLoading={swapping}
            cursor={warning ? 'not-allowed' : 'pointer'}
            onClick={swap}
          >
            {buy ? 'Buy' : 'Sell'}
          </Button>
        )}

        {trade && independentField === (buy ? Field.INPUT : Field.OUTPUT) ? (
          <SwapText>{buy ? 'at least' : 'at most'}</SwapText>
        ) : null}

        <AmountInput
          controlled={independentField === (buy ? Field.OUTPUT : Field.INPUT)}
          isDisabled={showUSD || swapping}
          isInvalid={isInvalidTrade || (!buy && isInvalidBalance)}
          value={
            showUSD && USDAmountsFormatted[buy ? Field.OUTPUT : Field.INPUT]
              ? `$${USDAmountsFormatted[buy ? Field.OUTPUT : Field.INPUT]}`
              : formatted[buy ? Field.OUTPUT : Field.INPUT]
          }
          onChange={(value): void => {
            dispatch({
              type: ActionType.TYPE,
              payload: { field: buy ? Field.OUTPUT : Field.INPUT, value },
            })
          }}
        />

        {!buy && canMax ? (
          <Button
            size="sm"
            mt="0.3rem"
            onClick={(): void => {
              dispatch({
                type: ActionType.TYPE,
                payload: { field: Field.INPUT, value: balance?.toExact() },
              })
            }}
          >
            Max
          </Button>
        ) : null}

        <TokenSelect
          tokenAddress={tokenAddresses[buy ? Field.OUTPUT : Field.INPUT].address}
          isInvalid={isInvalidRoute}
          isDisabled={swapping}
          onAddressSelect={(address): void => {
            dispatch({
              type: ActionType.SELECT_TOKEN,
              payload: { field: buy ? Field.OUTPUT : Field.INPUT, address },
            })
          }}
        />

        <SwapText>
          {buy ? 'with' : 'for'}
          {trade && independentField === (buy ? Field.OUTPUT : Field.INPUT) ? (buy ? ' at most' : ' at least') : ''}
        </SwapText>

        <AmountInput
          controlled={independentField === (buy ? Field.INPUT : Field.OUTPUT)}
          isDisabled={showUSD || swapping}
          isInvalid={isInvalidTrade || (buy && isInvalidBalance)}
          value={
            showUSD && USDAmountsFormatted[buy ? Field.INPUT : Field.OUTPUT]
              ? `$${USDAmountsFormatted[buy ? Field.INPUT : Field.OUTPUT]}`
              : formatted[buy ? Field.INPUT : Field.OUTPUT]
          }
          onChange={(value): void => {
            dispatch({
              type: ActionType.TYPE,
              payload: { field: buy ? Field.INPUT : Field.OUTPUT, value },
            })
          }}
        />

        {buy && canMax ? (
          <Button
            size="sm"
            mt="0.3rem"
            onClick={(): void => {
              dispatch({
                type: ActionType.TYPE,
                payload: { field: Field.INPUT, value: balance?.toExact() },
              })
            }}
          >
            Max
          </Button>
        ) : null}

        <TokenSelect
          tokenAddress={tokenAddresses[buy ? Field.INPUT : Field.OUTPUT].address}
          isInvalid={isInvalidRoute}
          isDisabled={swapping}
          onAddressSelect={(address): void => {
            dispatch({
              type: ActionType.SELECT_TOKEN,
              payload: { field: buy ? Field.INPUT : Field.OUTPUT, address },
            })
          }}
        />

        <SwapText fontSize="3xl" lineHeight={1} pt="0.3rem">
          .
        </SwapText>
      </Stack>

      <TradeSummary route={route} trade={trade} warning={warning} danger={danger} />
    </Stack>
  )
}