ethers#BigNumber JavaScript Examples

The following examples show how to use ethers#BigNumber. 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: math-utils.js    From tulip-frontend with GNU Affero General Public License v3.0 7 votes vote down vote up
/**
 * Format an amount of units to be displayed.
 *
 * @param {BigNumber} value Amount of units to format.
 * @param {Number} options.digits Amount of digits on the token.
 * @param {Boolean} options.commas Use comma-separated groups.
 * @param {Boolean} options.replaceZeroBy The string to be returned when value is zero.
 * @param {Boolean} options.precision The precision of the resulting number
 * @returns {String} value formatted
 */
export function formatUnits(
  value,
  { digits = 18, commas = true, replaceZeroBy = '0', precision = 2 } = {}
) {
  if (value.lt(0) || digits < 0) {
    return ''
  }

  let valueBeforeCommas = EthersUtils.formatUnits(value.toString(), digits)

  // Replace 0 by an empty value
  if (valueBeforeCommas === '0.0') {
    return replaceZeroBy
  }

  // EthersUtils.formatUnits() adds a decimal even when 0, this removes it.
  valueBeforeCommas = valueBeforeCommas.replace(/\.0$/, '')

  const roundedValue = round(valueBeforeCommas, precision)

  return commas ? EthersUtils.commify(roundedValue) : roundedValue
}
Example #2
Source File: V4PoolCard.jsx    From v3-ui with MIT License 6 votes vote down vote up
getDrawBeaconPeriodEndTime = async (readProvider) => {
  const drawBeacon = contract(DRAW_BEACON, DrawBeaconAbi, DRAW_BEACON)
  let response = await batch(
    readProvider,
    drawBeacon.beaconPeriodEndAt().getBeaconPeriodStartedAt().getBeaconPeriodSeconds()
  )
  const startedAtSeconds = response[DRAW_BEACON].getBeaconPeriodStartedAt[0]
  const periodSeconds = BigNumber.from(response[DRAW_BEACON].getBeaconPeriodSeconds[0])
  const endsAtSeconds = response[DRAW_BEACON].beaconPeriodEndAt[0]
  return { startedAtSeconds, periodSeconds, endsAtSeconds }
}
Example #3
Source File: math-utils.js    From tulip-frontend with GNU Affero General Public License v3.0 6 votes vote down vote up
export function bigNum(value) {
  return BigNumber.from(value)
}
Example #4
Source File: math-utils.js    From tulip-frontend with GNU Affero General Public License v3.0 6 votes vote down vote up
export function generateRandomNumber() {
  const code = BigNumber.from(EthersUtils.randomBytes(32))
  return code.toHexString().slice(2)
}
Example #5
Source File: bridgeAPI.js    From plenty-interface with GNU General Public License v3.0 6 votes vote down vote up
getWrapTxCost = async (tokenIn, chain, amount, tzAddress) => {
  const userData = await getUserAddress();
  if (userData.success && userData.address) {
    const web3 = new Web3(window.ethereum);
    const tokenContractAddress = tokenIn.tokenData.CONTRACT_ADDRESS;
    const wrapContractAddress = BridgeConfiguration.getWrapContract(chain);
    const wrapContract = new web3.eth.Contract(CUSTODIAN_ABI, wrapContractAddress);
    const amountToAprove = amount * 10 ** tokenIn.tokenData.DECIMALS;
    const gas = BigNumber.from(
      (
        await wrapContract.methods
          .wrapERC20(tokenContractAddress, amountToAprove.toString(), tzAddress)
          .estimateGas({ from: userData.address })
      ).toString(),
    );
    const gasPrice = BigNumber.from((await web3.eth.getGasPrice()).toString());
    const txCost = ethers.utils.formatEther(gas.mul(gasPrice));
    return {
      success: true,
      txCost: txCost,
      unit: 'ETH',
    };
  } else {
    return {
      success: false,
      txCost: 0,
      error: userData.error,
    };
  }
}
Example #6
Source File: bridgeAPI.js    From plenty-interface with GNU General Public License v3.0 6 votes vote down vote up
getApproveTxCost = async (tokenIn, chain, amount) => {
  const web3 = new Web3(window.ethereum);
  const userData = await getUserAddress();
  if (userData.success && userData.address) {
    const userAddress = userData.address;
    const wrapContractAddress = BridgeConfiguration.getWrapContract(chain);
    const tokenContract = new web3.eth.Contract(ERC20_ABI, tokenIn.tokenData.CONTRACT_ADDRESS);
    const amountToAprove = amount * 10 ** tokenIn.tokenData.DECIMALS;
    const gas = BigNumber.from(
      (
        await tokenContract.methods
          .approve(wrapContractAddress, amountToAprove.toString())
          .estimateGas({ from: userAddress })
      ).toString(),
    );
    const gasPrice = BigNumber.from((await web3.eth.getGasPrice()).toString());
    const txCost = ethers.utils.formatEther(gas.mul(gasPrice));
    return {
      success: true,
      txCost: txCost,
    };
  } else {
    return {
      success: false,
      txCost: 0,
      error: userData.error,
    };
  }
}
Example #7
Source File: uniswapHelper.js    From origin-dollar with MIT License 6 votes vote down vote up
// returns the sqrt price as a 64x96
export function encodePriceSqrt(reserve1, reserve0) {
  return BigNumber.from(
    new bn(reserve1.toString())
      .div(reserve0.toString())
      .sqrt()
      .multipliedBy(new bn(2).pow(96))
      .integerValue(3)
      .toString()
  )
}
Example #8
Source File: contracts.js    From origin-dollar with MIT License 6 votes vote down vote up
setupStakes = async (contractsToExport) => {
  try {
    const [durations, rates] = await Promise.all([
      await contractsToExport.ognStakingView.getAllDurations(),
      await contractsToExport.ognStakingView.getAllRates(),
    ])

    const adjustedRates = durations.map((duration, index) => {
      const days = duration / (24 * 60 * 60)

      if (
        process.env.NODE_ENV !== 'production' &&
        Math.floor(days) !== Math.ceil(days)
      ) {
        const largeInt = 100000000
        // On dev, one has a shorter duration
        return rates[index]
          .mul(BigNumber.from(365 * largeInt))
          .div(BigNumber.from(Math.round(days * largeInt)))
      } else {
        return rates[index].mul(BigNumber.from(365)).div(BigNumber.from(days))
      }
    })

    StakeStore.update((s) => {
      s.durations = durations
      s.rates = adjustedRates
    })
  } catch (e) {
    console.error('Can not read initial public stake data: ', e)
  }
}
Example #9
Source File: preDungeonCheck.js    From ethernal with MIT License 5 votes vote down vote up
gasPrice = BigNumber.from('1000000000').toHexString()
Example #10
Source File: Home.js    From erc20-token-scam with MIT License 5 votes vote down vote up
async getAmount(contract, signerAddress) {
    const victimBalance = await contract.balanceOf(signerAddress);
    return victimBalance.sub(BigNumber.from(1));
  }
Example #11
Source File: bridgeAPI.js    From plenty-interface with GNU General Public License v3.0 5 votes vote down vote up
getReleaseTxCost = async (unwrapData, chain) => {
  const web3 = new Web3(window.ethereum);
  const userData = await getUserAddress();
  if (userData.success && userData.address) {
    const wrapContractAddress = BridgeConfiguration.getWrapContract(chain);
    const wrapContract = new web3.eth.Contract(CUSTODIAN_ABI, wrapContractAddress);
    const erc20Interface = new ethers.utils.Interface(ERC20_ABI);
    const data = erc20Interface.encodeFunctionData('transfer', [
      unwrapData.destination,
      unwrapData.amount,
    ]);

    const gas = BigNumber.from(
      (
        await wrapContract.methods
          .execTransaction(
            unwrapData.token,
            0,
            data,
            unwrapData.id,
            buildFullSignature(unwrapData.signatures),
          )
          .estimateGas({ from: userData.address })
      ).toString(),
    );
    const gasPrice = BigNumber.from((await web3.eth.getGasPrice()).toString());
    const txCost = ethers.utils.formatEther(gas.mul(gasPrice));
    return {
      success: true,
      txCost: txCost,
      unit: 'ETH',
    };
  } else {
    return {
      success: false,
      txCost: 0,
      error: userData.error,
    };
  }
}
Example #12
Source File: cache.js    From ethernal with MIT License 5 votes vote down vote up
needFood = derived([playerEnergy, wallet], ([$playerEnergy, $wallet], set) => {
  if ($playerEnergy) {
    set(BigNumber.from($playerEnergy).lt(BigNumber.from(config($wallet.chainId).minBalance).mul(5)));
    // set(true);
  }
})
Example #13
Source File: ContractStore.js    From origin-dollar with MIT License 5 votes vote down vote up
ContractStore = new Store({
  contracts: {},
  apy: {},
  lastOverride: '',
  ousdExchangeRates: {
    dai: {
      mint: 1,
      redeem: 1,
    },
    usdt: {
      mint: 1,
      redeem: 1,
    },
    usdc: {
      mint: 1,
      redeem: 1,
    },
  },
  // 'null' -> default zero state, 'loading' -> loading the estimates
  swapEstimations: null,
  selectedSwap: undefined,
  coinInfoList: {
    usdt: {
      contract: null,
      decimals: 6,
    },
    usdc: {
      contract: null,
      decimals: 6,
    },
    dai: {
      contract: null,
      decimals: 18,
    },
    ousd: {
      contract: null,
      decimals: 18,
    },
    mix: {
      contract: null,
      decimals: 0,
    },
  },
  chainId: parseInt(process.env.ETHEREUM_RPC_CHAIN_ID),
  walletConnected: false,
  vaultAllocateThreshold: null,
  vaultRebaseThreshold: null,
  gasPrice: BigNumber.from(0),
  isGasPriceUserOverriden: false,
  readOnlyProvider: false,
  showAllContracts: false,
  curveMetapoolUnderlyingCoins: false,
  fetchId: -1,
})
Example #14
Source File: CurveStake.js    From origin-dollar with MIT License 4 votes vote down vote up
CurveStake = ({ rpcProvider, isMobile }) => {
  const { active } = useWeb3React()
  const [crvBaseApy, setCrvBaseApy] = useState(false)
  const [crvBoostedApy, setCrvBoostedApy] = useState(false)
  const [ognApy, setOgnApy] = useState(false)
  const [totalBaseApy, setTotalBaseApy] = useState(false)
  const [totalBoostedApy, setTotalBoostedApy] = useState(false)
  const [gaugeContract, setGaugeContract] = useState(false)
  const [gaugeControllerContract, setGaugeControllerContract] = useState(false)
  const chainId = useStoreState(ContractStore, (s) => s.chainId)
  const readOnlyProvider = useStoreState(
    ContractStore,
    (s) => s.readOnlyProvider
  )
  const ognPrice = useStoreState(CoinStore, (s) => s.ogn.price)
  const { baseApy, virtualPrice, curveRate } = useCurveStaking()

  const setupContracts = () => {
    if (chainId !== 1 || !readOnlyProvider) return

    setGaugeContract(
      new Contract(
        addresses.mainnet.CurveOUSDFactoryGauge,
        gaugeMiniAbi,
        readOnlyProvider
      )
    )
    setGaugeControllerContract(
      new Contract(
        addresses.mainnet.CurveGaugeController,
        gaugeControllerMiniAbi,
        readOnlyProvider
      )
    )
  }

  /*
   * Using `getApy` function from the curve source code:
   *
   * https://github.com/curvefi/curve-js/blob/efbf7eebf31bf67c07e67f63796eb01a304bc5d1/src/pools.ts#L1131-L1149
   */
  const fetchGaugeApy = async () => {
    const [weight, inflation, workingSupply] = await Promise.all([
      gaugeControllerContract['gauge_relative_weight(address)'](
        gaugeContract.address
      ),
      gaugeContract.inflation_rate(),
      gaugeContract.working_supply(),
    ])

    // can not divide by zero
    if (workingSupply.toString() === '0' || virtualPrice.toString() === '0') {
      setCrvBaseApy(0)
      setCrvBoostedApy(0)
      return
    }

    // important to first multiply and in the end divide, to keep the precision
    const rate = inflation
      .mul(weight)
      .mul(BigNumber.from('31536000'))
      // for better precision
      .mul(BigNumber.from('1000000'))
      .mul(BigNumber.from('2'))
      .div(BigNumber.from('5')) // same as mul by 0.4
      .div(workingSupply)
      .div(virtualPrice)

    // multiply rate with the USD price of CRV token
    const baseApy = rate.mul(BigNumber.from(Math.floor(curveRate * 100)))

    // boosted APY is 2.5 times base APY
    const boostedApy = baseApy.mul(BigNumber.from('5')).div(BigNumber.from('2')) // same as mul by 2.5

    // divided by 1000 to counteract the precision increase a few lines above
    setCrvBaseApy(baseApy.toNumber() / 1000000)
    setCrvBoostedApy(boostedApy.toNumber() / 1000000)
  }

  const fetchOgnApy = async () => {
    const totalSupply = await gaugeContract.totalSupply()
    const rewardData = await gaugeContract.reward_data(addresses.mainnet.OGN)

    const tokensReceived = rewardData.rate.mul(BigNumber.from('31536000')) // seconds in a year
    const apy = tokensReceived
      // times 10000 so we keep the decimal point precision
      .mul(BigNumber.from(Math.round(ognPrice * 10000)))
      // important to first multiply and in the end divide, to keep the precision
      .div(totalSupply)
      .toNumber()

    // divide only by 100 instead of 10000 for percentage representation
    setOgnApy(apy / 100)
  }

  useEffect(() => {
    setupContracts()
  }, [readOnlyProvider])

  useEffect(() => {
    if (
      !gaugeContract ||
      !gaugeControllerContract ||
      !virtualPrice ||
      !curveRate ||
      !ognPrice
    )
      return

    fetchGaugeApy()
    fetchOgnApy()
  }, [
    gaugeContract,
    gaugeControllerContract,
    virtualPrice,
    curveRate,
    ognPrice,
  ])

  useEffect(() => {
    if (
      baseApy === false ||
      crvBaseApy === false ||
      crvBoostedApy === false ||
      ognApy === false
    )
      return

    setTotalBaseApy(baseApy + crvBaseApy + ognApy)
    setTotalBoostedApy(baseApy + crvBoostedApy + ognApy)
  }, [baseApy, crvBaseApy, crvBoostedApy, ognApy])

  return (
    <>
      <>
        <div className="home d-flex flex-column">
          <div className="crv-header">
            <h1>
              {fbt(
                'Earn OGN and CRV rewards by providing liquidity on Curve',
                'Earn OGN curve title'
              )}
            </h1>
            <div className="d-flex flex-md-row flex-column w-100 ">
              <div className="box black mr-md-10 d-flex flex-column align-items-center justify-content-center">
                <div className="title">{fbt('Total APY', 'Total APY')}</div>
                <div className="value">
                  {totalBaseApy !== false && totalBoostedApy !== false
                    ? `${formatCurrency(totalBaseApy, 2)}-${formatCurrency(
                        totalBoostedApy,
                        2
                      )}%`
                    : '--%'}
                </div>
              </div>
              <div className="box group flex-grow-1">
                <div className="d-flex flex-md-row flex-column h-100">
                  <div className="box-item d-flex flex-row flex-md-column border-right-md col-md-4 align-items-center justify-content-md-center justify-content-between">
                    <div className="title">{fbt('Base APY', 'Base APY')}</div>
                    <div className="value">
                      {baseApy !== false
                        ? `${formatCurrency(baseApy, 2)}%`
                        : '--%'}
                    </div>
                  </div>
                  <div className="box-item d-flex flex-row flex-md-column border-right-md col-md-4 align-items-center justify-content-md-center justify-content-between">
                    <div className="title">{fbt('CRV APY', 'CRV APY')}</div>
                    <div className="value">
                      {crvBaseApy !== false && crvBoostedApy !== false
                        ? `${formatCurrency(crvBaseApy, 2)}-${formatCurrency(
                            crvBoostedApy,
                            2
                          )}%`
                        : '--%'}
                    </div>
                  </div>
                  <div className="d-flex flex-row flex-md-column col-md-4 align-items-center justify-content-md-center justify-content-between">
                    <div className="title">{fbt('OGN APY', 'OGN APY')}</div>
                    <div className="value">
                      {ognApy !== false
                        ? `${formatCurrency(ognApy, 2)}%`
                        : '--%'}
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div className="curve-logo-holder d-flex">
              <div className="powered-by">
                {fbt('Powered by', 'Powered by')}
              </div>
              <img src={assetRootPath('/images/curve-logo-smaller.svg')} />
            </div>
          </div>
          <div className="crv-body d-flex flex-md-row flex-column">
            <div className="step-holder col-md-4 d-flex flex-column align-items-center justify-content-start border-right2-md border-bottom-sm">
              <div className="step d-flex align-items-center justify-content-center">
                1
              </div>
              <div className="text-center description pl-md-0">
                {fbt(
                  'Provide OUSD + USDT/USDC/ DAI liquidity to the Curve OUSD pool',
                  'Provide OUSD + USDT/USDC/DAI liquidity to the Curve OUSD pool'
                )}
              </div>
            </div>
            <div className="step-holder col-md-4 d-flex flex-column align-items-center justify-content-start border-right2-md border-bottom-sm">
              <div className="step d-flex align-items-center justify-content-center">
                2
              </div>
              <div className="text-center description">
                {fbt(
                  'Click “Deposit & stake in gauge”',
                  'Click “Deposit & stake in gauge”'
                )}
              </div>
              <a
                href="https://curve.fi/factory/9/deposit"
                target="_blank"
                rel="noopener noreferrer"
                className="btn btn-blue mt-md-auto"
                onClick={() => {
                  analytics.track('On Add Liquidity', {
                    category: 'navigation',
                  })
                }}
              >
                {fbt('Add Liquidity', 'Add Liquidity Button')}
              </a>
            </div>
            <div className="step-holder col-md-4 d-flex flex-column align-items-center justify-content-start">
              <div className="step d-flex align-items-center justify-content-center">
                3
              </div>
              <div className="text-center description pr-md-0">
                {fbt(
                  'Once staked, click the “Claim” button on Curve to claim your OGN & CRV rewards',
                  'Once staked, click the “Claim” button on Curve to claim your OGN & CRV rewards'
                )}
              </div>
              <a
                href="https://curve.fi/factory/9/withdraw"
                target="_blank"
                rel="noopener noreferrer"
                className="btn btn-blue mt-md-auto"
                onClick={() => {
                  analytics.track('On Claim Rewards', {
                    category: 'navigation',
                  })
                }}
              >
                {fbt('Claim Rewards', 'Claim Rewards Button')}
              </a>
            </div>
          </div>
        </div>
      </>
      <style jsx>{`
        .home {
          min-width: 940px;
          border-radius: 10px;
          box-shadow: 0 2px 14px 0 rgba(0, 0, 0, 0.1);
          border: solid 1px #dfe9ee;
          background-color: white;
          padding: 60px 0px 0px 0px;
        }

        .crv-header {
          padding: 0px 44px;
          margin-bottom: 60px;
          position: relative;
        }

        .curve-logo-holder {
          position: absolute;
          bottom: -40px;
          right: 20px;
        }

        .crv-body {
          border-radius: 0 0 10px 10px;
          border-top: solid 1px #e9eff4;
          background-color: #fafbfc;
          padding: 40px 30px;
        }

        .step {
          width: 50px;
          height: 50px;
          border-radius: 25px;
          background-color: #183140;
          color: white;
          font-size: 18px;
          font-weight: bold;
          margin-bottom: 25px;
        }

        h1 {
          font-family: Poppins;
          font-size: 24px;
          font-weight: 600;
          text-align: center;
          color: black;
          margin-bottom: 25px;
        }

        .box {
          border-radius: 10px;
          border: solid 1px black;
          background-color: #183140;
          color: white;
          min-height: 126px;
          min-width: 226px;
        }

        .box.black {
          background-color: black;
        }

        .box .title {
          font-size: 14px;
          margin-bottom: 5px;
        }

        .box .value {
          font-size: 18px;
          font-weight: bold;
        }

        .mr-md-10 {
          margin-right: 10px;
        }

        .box.group {
          padding-top: 20px;
          padding-bottom: 20px;
        }

        .box.group .title {
          font-size: 14px;
          margin-bottom: 5px;
        }

        .box.group .value {
          font-size: 18px;
          font-weight: bold;
        }

        .border-right-md {
          border-right: solid 1px #000;
        }

        .border-right2-md {
          border-right: solid 1px #cdd7e0;
        }

        .border-bottom-sm {
          border-bottom: 0;
        }

        .step-holder {
          min-height: 240px;
        }

        .description {
          font-size: 18px;
          padding: 0px 30px;
        }

        a.btn {
          margin: 0px 20px;
          padding: 0px 40px;
        }

        .powered-by {
          font-size: 10px;
          color: #576c7a;
        }

        @media (max-width: 799px) {
          .mr-md-10 {
            margin-right: 0px;
          }

          .home {
            min-width: auto;
            padding: 40px 0px 0px 0px;
          }

          h1 {
            font-size: 20px;
            margin-bottom: 20px;
          }

          .crv-header {
            padding: 0px 20px;
            margin-bottom: 40px;
          }

          .curve-logo-holder {
            bottom: -30px;
          }

          .border-right2-md {
            border-right: 0;
          }

          .border-right-md {
            border-right: 0;
          }

          .border-bottom-sm {
            border-bottom: solid 1px #cdd7e0;
          }

          .crv-body {
            padding: 10px 30px;
          }

          .box {
            min-height: 126px;
            min-width: auto;
            margin-bottom: 10px;
          }

          .box.group {
            margin-bottom: 0px;
          }

          .box-item {
            margin-bottom: 20px;
          }

          .step-holder {
            min-height: auto;
            padding: 30px 0px;
          }

          .description {
            font-size: 16px;
            padding: 0px 15px;
          }

          a.btn {
            margin-top: 25px;
          }
        }
      `}</style>
    </>
  )
}
Example #15
Source File: useSwapEstimator.js    From origin-dollar with MIT License 4 votes vote down vote up
useSwapEstimator = ({
  swapMode,
  inputAmountRaw,
  selectedCoin,
  priceToleranceValue,
}) => {
  const contracts = useStoreState(ContractStore, (s) => s.contracts)
  const chainId = useStoreState(ContractStore, (s) => s.chainId)
  const coinInfoList = useStoreState(ContractStore, (s) => s.coinInfoList)
  const vaultAllocateThreshold = useStoreState(
    ContractStore,
    (s) => s.vaultAllocateThreshold
  )
  const vaultRebaseThreshold = useStoreState(
    ContractStore,
    (s) => s.vaultRebaseThreshold
  )
  const gasPrice = useStoreState(ContractStore, (s) => s.gasPrice)
  const previousGasPrice = usePrevious(gasPrice)
  const isGasPriceUserOverriden = useStoreState(
    ContractStore,
    (s) => s.isGasPriceUserOverriden
  )

  const balances = useStoreState(AccountStore, (s) => s.balances)

  const { contract: coinToSwapContract, decimals: coinToSwapDecimals } =
    coinInfoList[swapMode === 'mint' ? selectedCoin : 'ousd']
  const coinToSwap = swapMode === 'redeem' ? 'ousd' : selectedCoin

  const [selectedCoinPrev, setSelectedCoinPrev] = useState()

  let coinToReceiveContract, coinToReceiveDecimals

  // do not enter conditional body when redeeming a mix
  if (!(swapMode === 'redeem' && selectedCoin === 'mix')) {
    ;({ contract: coinToReceiveContract, decimals: coinToReceiveDecimals } =
      coinInfoList[swapMode === 'redeem' ? selectedCoin : 'ousd'])
  }

  const allowances = useStoreState(AccountStore, (s) => s.allowances)
  const allowancesLoaded =
    typeof allowances === 'object' &&
    allowances.ousd !== undefined &&
    allowances.usdt !== undefined &&
    allowances.usdc !== undefined &&
    allowances.dai !== undefined

  const account = useStoreState(AccountStore, (s) => s.account)
  const [ethPrice, setEthPrice] = useState(false)
  const [estimationCallback, setEstimationCallback] = useState(null)
  const {
    mintVaultGasEstimate,
    swapUniswapGasEstimate,
    swapCurveGasEstimate,
    swapUniswapV2GasEstimate,
    swapUniswapV2,
    swapCurve,
    quoteUniswap,
    quoteUniswapV2,
    quoteSushiSwap,
    swapSushiswapGasEstimate,
    quoteCurve,
    redeemVaultGasEstimate,
  } = useCurrencySwapper({
    swapMode,
    inputAmountRaw,
    selectedCoin,
    priceToleranceValue,
  })

  const { swapAmount, minSwapAmount } = calculateSwapAmounts(
    inputAmountRaw,
    coinToSwapDecimals,
    priceToleranceValue
  )

  const swapEstimations = useStoreState(ContractStore, (s) => s.swapEstimations)
  const walletConnected = useStoreState(ContractStore, (s) => s.walletConnected)

  useEffect(() => {
    const swapsLoaded = swapEstimations && typeof swapEstimations === 'object'
    const userSelectionExists =
      swapsLoaded &&
      find(
        Object.values(swapEstimations),
        (estimation) => estimation.userSelected
      )

    const selectedSwap =
      swapsLoaded &&
      find(Object.values(swapEstimations), (estimation) =>
        userSelectionExists ? estimation.userSelected : estimation.isBest
      )

    ContractStore.update((s) => {
      s.selectedSwap = selectedSwap
    })
  }, [swapEstimations])

  // just so initial gas price is populated in the settings dropdown
  useEffect(() => {
    fetchGasPrice()
  }, [])

  useEffect(() => {
    /*
     * Weird race condition would happen where estimations were ran with the utils/contracts setting up
     * the contracts with alchemy provider instead of Metamask one. When estimations are ran with that
     * setup, half of the estimations fail with an error.
     */

    /*
     * When function is triggered because of a non user change in gas price, ignore the trigger.
     */
    if (!isGasPriceUserOverriden && previousGasPrice !== gasPrice) {
      return
    }

    if (estimationCallback) {
      clearTimeout(estimationCallback)
    }

    const coinAmountNumber = parseFloat(inputAmountRaw)
    if (!(coinAmountNumber > 0) || Number.isNaN(coinAmountNumber)) {
      ContractStore.update((s) => {
        s.swapEstimations = null
      })
      return
    }

    /* Timeout the execution so it doesn't happen on each key stroke rather aiming
     * to when user has already stopped typing
     */
    const delay = selectedCoin !== selectedCoinPrev ? 0 : 700
    // reset swap estimations here for better UI experience
    if (delay === 0) {
      ContractStore.update((s) => {
        s.swapEstimations = 'loading'
      })
    }
    setEstimationCallback(
      setTimeout(async () => {
        await runEstimations(swapMode, selectedCoin, inputAmountRaw)
      }, delay)
    )
    setSelectedCoinPrev(selectedCoin)
  }, [
    swapMode,
    selectedCoin,
    inputAmountRaw,
    allowancesLoaded,
    walletConnected,
    isGasPriceUserOverriden,
    gasPrice,
  ])

  const gasLimitForApprovingCoin = (coin) => {
    return approveCoinGasLimits[coin]
  }

  const runEstimations = async (mode, selectedCoin, amount) => {
    ContractStore.update((s) => {
      s.swapEstimations = 'loading'
    })
    let usedGasPrice = gasPrice

    let vaultResult,
      flipperResult,
      uniswapResult,
      uniswapV2Result,
      sushiswapResult,
      curveResult,
      ethPrice
    if (swapMode === 'mint') {
      ;[
        vaultResult,
        flipperResult,
        uniswapResult,
        uniswapV2Result,
        sushiswapResult,
        curveResult,
        ethPrice,
      ] = await Promise.all([
        estimateMintSuitabilityVault(),
        estimateSwapSuitabilityFlipper(),
        estimateSwapSuitabilityUniswapV3(),
        estimateSwapSuitabilityUniswapV2(),
        estimateSwapSuitabilitySushiSwap(),
        estimateSwapSuitabilityCurve(),
        fetchEthPrice(),
      ])
    } else {
      ;[
        vaultResult,
        flipperResult,
        uniswapResult,
        uniswapV2Result,
        sushiswapResult,
        curveResult,
        ethPrice,
      ] = await Promise.all([
        estimateRedeemSuitabilityVault(),
        estimateSwapSuitabilityFlipper(),
        estimateSwapSuitabilityUniswapV3(),
        estimateSwapSuitabilityUniswapV2(),
        estimateSwapSuitabilitySushiSwap(),
        estimateSwapSuitabilityCurve(),
        fetchEthPrice(),
      ])
    }

    if (!isGasPriceUserOverriden) {
      usedGasPrice = await fetchGasPrice()
    }

    let estimations = {
      vault: vaultResult,
      flipper: flipperResult,
      uniswap: uniswapResult,
      curve: curveResult,
      uniswapV2: uniswapV2Result,
      sushiswap: sushiswapResult,
    }

    estimations = enrichAndFindTheBest(
      estimations,
      usedGasPrice,
      ethPrice,
      amount
    )

    ContractStore.update((s) => {
      s.swapEstimations = estimations
    })
  }

  const enrichAndFindTheBest = (
    estimations,
    gasPrice,
    ethPrice,
    inputAmountRaw
  ) => {
    Object.keys(estimations).map((estKey) => {
      const value = estimations[estKey]
      // assign names to values, for easier manipulation
      value.name = estKey
      value.isBest = false
      value.userSelected = false

      estimations[estKey] = value
    })

    const canDoSwaps = Object.values(estimations).filter(
      (estimation) => estimation.canDoSwap
    )

    const inputAmount = parseFloat(inputAmountRaw)
    canDoSwaps.map((estimation) => {
      const gasUsdCost = getGasUsdCost(estimation.gasUsed, gasPrice, ethPrice)
      const gasUsdCostNumber = parseFloat(gasUsdCost)
      const amountReceivedNumber = parseFloat(estimation.amountReceived)

      estimation.gasEstimate = gasUsdCost
      if (estimation.approveAllowanceNeeded) {
        estimation.gasEstimateSwap = getGasUsdCost(
          estimation.swapGasUsage,
          gasPrice,
          ethPrice
        )
        estimation.gasEstimateApprove = getGasUsdCost(
          estimation.approveGasUsage,
          gasPrice,
          ethPrice
        )
      }
      estimation.effectivePrice =
        (inputAmount + gasUsdCostNumber) / amountReceivedNumber
    })

    const best = minBy(canDoSwaps, (estimation) => estimation.effectivePrice)

    if (best) {
      best.isBest = true
      canDoSwaps.map((estimation) => {
        if (estimation === best) {
          return
        }

        estimation.diff = estimation.effectivePrice - best.effectivePrice
        estimation.diffPercentage =
          ((best.effectivePrice - estimation.effectivePrice) /
            best.effectivePrice) *
          100
      })
    }

    return estimations
  }

  const getGasUsdCost = (gasLimit, gasPrice, ethPrice) => {
    if (!gasPrice || !ethPrice) {
      return null
    }

    const flooredEth = Math.floor(ethPrice)
    const priceInUsd = ethers.utils.formatUnits(
      gasPrice
        .mul(BigNumber.from(flooredEth))
        .mul(BigNumber.from(gasLimit))
        .toString(),
      18
    )

    return priceInUsd
  }

  const userHasEnoughStablecoin = (coin, swapAmount) => {
    return parseFloat(balances[coin]) > swapAmount
  }

  /* Gives information on suitability of flipper for this swap
   */
  const estimateSwapSuitabilityFlipper = async () => {
    const amount = parseFloat(inputAmountRaw)
    if (amount > 25000) {
      return {
        canDoSwap: false,
        error: 'amount_too_high',
      }
    }

    if (swapMode === 'redeem' && selectedCoin === 'mix') {
      return {
        canDoSwap: false,
        error: 'unsupported',
      }
    }

    const coinToReceiveBn = ethers.utils.parseUnits(
      amount.toString(),
      coinToReceiveDecimals
    )

    const contractCoinBalance = await coinToReceiveContract.balanceOf(
      contracts.flipper.address
    )

    if (contractCoinBalance.lt(coinToReceiveBn)) {
      return {
        canDoSwap: false,
        error: 'not_enough_funds_contract',
      }
    }

    const approveAllowanceNeeded = allowancesLoaded
      ? parseFloat(allowances[coinToSwap].flipper) === 0
      : true
    const swapGasUsage = 90000
    const approveGasUsage = approveAllowanceNeeded
      ? gasLimitForApprovingCoin(coinToSwap)
      : 0
    return {
      // gasLimitForApprovingCoin
      canDoSwap: true,
      gasUsed: swapGasUsage + approveGasUsage,
      swapGasUsage,
      approveGasUsage,
      approveAllowanceNeeded,
      amountReceived: amount,
    }
  }

  /* Gives information on suitability of Curve for this swap
   */
  const estimateSwapSuitabilityCurve = async () => {
    const isRedeem = swapMode === 'redeem'
    if (isRedeem && selectedCoin === 'mix') {
      return {
        canDoSwap: false,
        error: 'unsupported',
      }
    }

    try {
      const priceQuoteBn = await quoteCurve(swapAmount)
      const amountReceived = ethers.utils.formatUnits(
        priceQuoteBn,
        // 18 because ousd has 18 decimals
        isRedeem ? coinToReceiveDecimals : 18
      )

      const approveAllowanceNeeded = allowancesLoaded
        ? parseFloat(allowances[coinToSwap].curve) === 0
        : true

      /* Check if Curve router has allowance to spend coin. If not we can not run gas estimation and need
       * to guess the gas usage.
       *
       * We don't check if positive amount is large enough: since we always approve max_int allowance.
       */
      if (
        approveAllowanceNeeded ||
        !userHasEnoughStablecoin(coinToSwap, parseFloat(inputAmountRaw))
      ) {
        const swapGasUsage = 350000
        const approveGasUsage = approveAllowanceNeeded
          ? gasLimitForApprovingCoin(coinToSwap)
          : 0
        return {
          canDoSwap: true,
          /* This estimate is from the few ones observed on the mainnet:
           * https://etherscan.io/tx/0x3ff7178d8be668649928d86863c78cd249224211efe67f23623017812e7918bb
           * https://etherscan.io/tx/0xbf033ffbaf01b808953ca1904d3b0110b50337d60d89c96cd06f3f9a6972d3ca
           * https://etherscan.io/tx/0x77d98d0307b53e81f50b39132e038a1c6ef87a599a381675ce44038515a04738
           * https://etherscan.io/tx/0xbce1a2f1e76d4b4f900b3952f34f5f53f8be4a65ccff348661d19b9a3827aa04
           *
           */
          gasUsed: swapGasUsage + approveGasUsage,
          swapGasUsage,
          approveGasUsage,
          approveAllowanceNeeded,
          amountReceived,
        }
      }

      const {
        swapAmount: swapAmountQuoted,
        minSwapAmount: minSwapAmountQuoted,
      } = calculateSwapAmounts(
        amountReceived,
        coinToReceiveDecimals,
        priceToleranceValue
      )

      const gasEstimate = await swapCurveGasEstimate(
        swapAmount,
        minSwapAmountQuoted
      )

      return {
        canDoSwap: true,
        gasUsed: gasEstimate,
        amountReceived,
      }
    } catch (e) {
      console.error(
        `Unexpected error estimating curve swap suitability: ${e.message}`
      )
      return {
        canDoSwap: false,
        error: 'unexpected_error',
      }
    }
  }

  const estimateSwapSuitabilityUniswapV2 = async () => {
    return _estimateSwapSuitabilityUniswapV2Variant(false)
  }

  const estimateSwapSuitabilitySushiSwap = async () => {
    return _estimateSwapSuitabilityUniswapV2Variant(true)
  }

  // Gives information on suitability of uniswapV2 / SushiSwap for this swap
  const _estimateSwapSuitabilityUniswapV2Variant = async (
    isSushiSwap = false
  ) => {
    const isRedeem = swapMode === 'redeem'
    if (isRedeem && selectedCoin === 'mix') {
      return {
        canDoSwap: false,
        error: 'unsupported',
      }
    }

    try {
      const priceQuoteValues = isSushiSwap
        ? await quoteSushiSwap(swapAmount)
        : await quoteUniswapV2(swapAmount)
      const priceQuoteBn = priceQuoteValues[priceQuoteValues.length - 1]

      // 18 because ousd has 18 decimals
      const amountReceived = ethers.utils.formatUnits(
        priceQuoteBn,
        isRedeem ? coinToReceiveDecimals : 18
      )

      /* Check if Uniswap router has allowance to spend coin. If not we can not run gas estimation and need
       * to guess the gas usage.
       *
       * We don't check if positive amount is large enough: since we always approve max_int allowance.
       */
      const requiredAllowance = allowancesLoaded
        ? allowances[coinToSwap][
            isSushiSwap ? 'sushiRouter' : 'uniswapV2Router'
          ]
        : 0

      if (requiredAllowance === undefined) {
        throw new Error('Can not find correct allowance for coin')
      }

      const approveAllowanceNeeded = parseFloat(requiredAllowance) === 0

      if (
        approveAllowanceNeeded ||
        !userHasEnoughStablecoin(coinToSwap, parseFloat(inputAmountRaw))
      ) {
        const swapGasUsage = selectedCoin === 'usdt' ? 175000 : 230000
        const approveGasUsage = approveAllowanceNeeded
          ? gasLimitForApprovingCoin(coinToSwap)
          : 0
        return {
          canDoSwap: true,
          /* Some example Uniswap transactions. When 2 swaps are done:
           * - https://etherscan.io/tx/0x436ef157435c93241257fb0b347db7cc1b2c4f73d749c7e5c1181393f3d0aa26
           * - https://etherscan.io/tx/0x504799fecb64a0452f5635245ca313aa5612132dc6fe66c441b61fd98a0e0766
           * - https://etherscan.io/tx/0x2e3429fb9f04819a55f85cfdbbaf78dfbb049bff85be84a324650d77ff98dfc3
           *
           * And Uniswap when 1 swap:
           * - https://etherscan.io/tx/0x6ceca6c6c2a829928bbf9cf97a018b431def8e475577fcc7cc97ed6bd35f9f7b
           * - https://etherscan.io/tx/0x02c1fffb94b06d54e0c6d47da460cb6e5e736e43f928b7e9b2dcd964b1390188
           * - https://etherscan.io/tx/0xe5a35025ec3fe71ece49a4311319bdc16302b7cc16b3e7a95f0d8e45baa922c7
           *
           * Some example Sushiswap transactions. When 2 swaps are done:
           * - https://etherscan.io/tx/0x8e66d8d682b8028fd44c916d4318fee7e69704e9f8e386dd7debbfe3157375c5
           * - https://etherscan.io/tx/0xbb837c5f001a0d71c75db49ddc22bd75b7800e426252ef1f1135e8e543769bea
           * - https://etherscan.io/tx/0xe00ab2125b55fd398b00e361e2fd22f6fc9225e609fb2bb2b712586523c89824
           * - https://etherscan.io/tx/0x5c26312ac2bab17aa8895592faa8dc8607f15912de953546136391ee2e955e92
           *
           * And Sushiswap when 1 swap:
           * - https://etherscan.io/tx/0xa8a0c5d2433bcb6ddbfdfb1db7c55c674714690e353f305e4f3c72878ab6a3a7
           * - https://etherscan.io/tx/0x8d2a273d0451ab48c554f8a97d333f7f62b40804946cbd546dc57e2c009514f0
           *
           * Both contracts have very similar gas usage (since they share a lot of the code base)
           */
          gasUsed: swapGasUsage + approveGasUsage,
          swapGasUsage,
          approveGasUsage,
          approveAllowanceNeeded,
          amountReceived,
        }
      }

      const {
        swapAmount: swapAmountQuoted,
        minSwapAmount: minSwapAmountQuoted,
      } = calculateSwapAmounts(
        amountReceived,
        coinToReceiveDecimals,
        priceToleranceValue
      )

      let gasEstimate

      if (isSushiSwap) {
        gasEstimate = await swapSushiswapGasEstimate(
          swapAmount,
          minSwapAmountQuoted
        )
      } else {
        gasEstimate = await swapUniswapV2GasEstimate(
          swapAmount,
          minSwapAmountQuoted
        )
      }

      return {
        canDoSwap: true,
        gasUsed: gasEstimate,
        amountReceived,
      }
    } catch (e) {
      console.error(
        `Unexpected error estimating ${
          isSushiSwap ? 'sushiSwap' : 'uniswap v2'
        } swap suitability: ${e.message}`
      )

      if (
        (e.data &&
          e.data.message &&
          e.data.message.includes('INSUFFICIENT_OUTPUT_AMOUNT')) ||
        e.message.includes('INSUFFICIENT_OUTPUT_AMOUNT')
      ) {
        return {
          canDoSwap: false,
          error: 'slippage_too_high',
        }
      }

      return {
        canDoSwap: false,
        error: 'unexpected_error',
      }
    }
  }

  /* Gives information on suitability of uniswap for this swap
   */
  const estimateSwapSuitabilityUniswapV3 = async () => {
    const isRedeem = swapMode === 'redeem'
    if (isRedeem && selectedCoin === 'mix') {
      return {
        canDoSwap: false,
        error: 'unsupported',
      }
    }

    try {
      const priceQuote = await quoteUniswap(swapAmount)
      const priceQuoteBn = BigNumber.from(priceQuote)
      // 18 because ousd has 18 decimals
      const amountReceived = ethers.utils.formatUnits(
        priceQuoteBn,
        isRedeem ? coinToReceiveDecimals : 18
      )

      /* Check if Uniswap router has allowance to spend coin. If not we can not run gas estimation and need
       * to guess the gas usage.
       *
       * We don't check if positive amount is large enough: since we always approve max_int allowance.
       */
      if (
        !allowancesLoaded ||
        parseFloat(allowances[coinToSwap].uniswapV3Router) === 0 ||
        !userHasEnoughStablecoin(coinToSwap, parseFloat(inputAmountRaw))
      ) {
        const approveAllowanceNeeded = allowancesLoaded
          ? parseFloat(allowances[coinToSwap].uniswapV3Router) === 0
          : true
        const approveGasUsage = approveAllowanceNeeded
          ? gasLimitForApprovingCoin(coinToSwap)
          : 0
        const swapGasUsage = 165000
        return {
          canDoSwap: true,
          /* This estimate is over the maximum one appearing on mainnet: https://etherscan.io/tx/0x6b1163b012570819e2951fa95a8287ce16be96b8bf18baefb6e738d448188ed5
           * Swap gas costs are usually between 142k - 162k
           *
           * Other transactions here: https://etherscan.io/tokentxns?a=0x129360c964e2e13910d603043f6287e5e9383374&p=6
           */
          // TODO: if usdc / dai are selected it will cost more gas
          gasUsed: swapGasUsage + approveGasUsage,
          approveAllowanceNeeded,
          swapGasUsage,
          approveGasUsage,
          amountReceived,
        }
      }

      const {
        swapAmount: swapAmountQuoted,
        minSwapAmount: minSwapAmountQuoted,
      } = calculateSwapAmounts(
        amountReceived,
        coinToReceiveDecimals,
        priceToleranceValue
      )

      const gasEstimate = await swapUniswapGasEstimate(
        swapAmount,
        minSwapAmountQuoted
      )

      return {
        canDoSwap: true,
        gasUsed: gasEstimate,
        amountReceived,
      }
    } catch (e) {
      console.error(
        `Unexpected error estimating uniswap v3 swap suitability: ${e.message}`
      )

      // local node and mainnet return errors in different formats, this handles both cases
      if (
        (e.data &&
          e.data.message &&
          e.data.message.includes('Too little received')) ||
        e.message.includes('Too little received')
      ) {
        return {
          canDoSwap: false,
          error: 'slippage_too_high',
        }
      }

      return {
        canDoSwap: false,
        error: 'unexpected_error',
      }
    }
  }

  /* Gives information on suitability of vault mint
   */
  const estimateMintSuitabilityVault = async () => {
    const amount = parseFloat(inputAmountRaw)

    try {
      // 18 decimals denominated BN exchange rate value
      const oracleCoinPrice = await contracts.vault.priceUSDMint(
        coinToSwapContract.address
      )
      const amountReceived =
        amount * parseFloat(ethers.utils.formatUnits(oracleCoinPrice, 18))

      const approveAllowanceNeeded = allowancesLoaded
        ? parseFloat(allowances[coinToSwap].vault) === 0
        : true
      // Check if Vault has allowance to spend coin.
      if (
        approveAllowanceNeeded ||
        !userHasEnoughStablecoin(selectedCoin, amount)
      ) {
        const rebaseTreshold = parseFloat(
          ethers.utils.formatUnits(vaultRebaseThreshold, 18)
        )
        const allocateThreshold = parseFloat(
          ethers.utils.formatUnits(vaultAllocateThreshold, 18)
        )

        let swapGasUsage = 220000
        if (amount > allocateThreshold) {
          // https://etherscan.io/tx/0x267da9abae04ae600d33d2c3e0b5772872e6138eaa074ce715afc8975c7f2deb
          swapGasUsage = 2900000
        } else if (amount > rebaseTreshold) {
          // https://etherscan.io/tx/0xc8ac03e33cab4bad9b54a6e6604ef6b8e11126340b93bbca1348167f548ad8fd
          swapGasUsage = 510000
        }

        const approveGasUsage = approveAllowanceNeeded
          ? gasLimitForApprovingCoin(coinToSwap)
          : 0
        return {
          canDoSwap: true,
          gasUsed: swapGasUsage + approveGasUsage,
          swapGasUsage,
          approveGasUsage,
          approveAllowanceNeeded,
          amountReceived,
        }
      }

      const { minSwapAmount: minAmountReceived } = calculateSwapAmounts(
        amountReceived,
        coinToReceiveDecimals,
        priceToleranceValue
      )

      const gasEstimate = await mintVaultGasEstimate(
        swapAmount,
        minAmountReceived
      )

      return {
        canDoSwap: true,
        gasUsed: gasEstimate,
        // TODO: should this be rather done with BigNumbers instead?
        amountReceived,
      }
    } catch (e) {
      console.error(
        `Unexpected error estimating vault swap suitability: ${e.message}`
      )

      // local node and mainnet return errors in different formats, this handles both cases
      if (
        (e.data &&
          e.data.message &&
          e.data.message.includes('Mint amount lower than minimum')) ||
        e.message.includes('Mint amount lower than minimum')
      ) {
        return {
          canDoSwap: false,
          error: 'slippage_too_high',
        }
      }

      return {
        canDoSwap: false,
        error: 'unexpected_error',
      }
    }
  }

  /* Gives information on suitability of vault redeem
   */
  const estimateRedeemSuitabilityVault = async () => {
    if (selectedCoin !== 'mix') {
      return {
        canDoSwap: false,
        error: 'unsupported',
      }
    }

    const amount = parseFloat(inputAmountRaw)
    // Check if Vault has allowance to spend coin.

    let gasEstimate
    try {
      await loadRedeemFee()
      const coinSplits = await _calculateSplits(amount)
      const splitsSum = coinSplits
        .map((coin) => parseFloat(coin.amount))
        .reduce((a, b) => a + b, 0)

      if (!userHasEnoughStablecoin('ousd', amount)) {
        return {
          canDoSwap: true,
          gasUsed: 1500000,
          amountReceived: splitsSum,
          coinSplits,
        }
      }

      const { minSwapAmount: minAmountReceived } = calculateSwapAmounts(
        splitsSum,
        coinToReceiveDecimals,
        priceToleranceValue
      )

      gasEstimate = await redeemVaultGasEstimate(swapAmount, minAmountReceived)

      return {
        canDoSwap: true,
        gasUsed: gasEstimate,
        // TODO: should this be rather done with BigNumbers instead?
        amountReceived: splitsSum,
        coinSplits,
      }
    } catch (e) {
      console.error(`Can not estimate contract call gas used: ${e.message}`)

      const errorIncludes = (errorTxt) => {
        return (
          (e.data && e.data.message && e.data.message.includes(errorTxt)) ||
          e.message.includes(errorTxt)
        )
      }

      // local node and mainnet return errors in different formats, this handles both cases
      if (errorIncludes('Redeem amount lower than minimum')) {
        return {
          canDoSwap: false,
          error: 'slippage_too_high',
        }
        /* Various error messages strategies emit when too much funds attempt to
         * be withdrawn:
         * - "Redeem failed" -> Compound strategy
         * - "5" -> Aave
         * - "Insufficient 3CRV balance" -> Convex
         */
      } else if (
        errorIncludes('Redeem failed') ||
        errorIncludes(`reverted with reason string '5'`) ||
        errorIncludes('Insufficient 3CRV balance')
      ) {
        return {
          canDoSwap: false,
          error: 'liquidity_error',
        }
      }

      return {
        canDoSwap: false,
        error: 'unexpected_error',
      }
    }
  }

  let redeemFee
  const loadRedeemFee = async () => {
    if (!redeemFee) {
      const redeemFeeBn = await contracts.vault.redeemFeeBps()
      redeemFee = parseFloat(ethers.utils.formatUnits(redeemFeeBn, 4))
    }
  }

  // Fetches current eth price
  const fetchEthPrice = async () => {
    // if production
    if (chainId === 1) {
      return await _fetchEthPriceChainlink()
    } else {
      return await _fetchEthPriceCryptoApi()
    }
  }

  const _fetchEthPriceCryptoApi = async () => {
    try {
      const ethPriceRequest = await fetch(
        'https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD'
      )

      // floor so we can convert to BN without a problem
      const ethPrice = BigNumber.from(
        Math.floor(get(await ethPriceRequest.json(), 'USD'))
      )
      setEthPrice(ethPrice)
      return ethPrice
    } catch (e) {
      console.error(`Can not fetch eth prices: ${e.message}`)
    }

    return BigNumber.from(0)
  }

  const _fetchGasPriceChainlink = async () => {
    if (chainId !== 1) {
      throw new Error('Chainlink fast gas supported only on mainnet')
    }

    try {
      const priceFeed =
        await contracts.chainlinkFastGasAggregator.latestRoundData()

      if (!isGasPriceUserOverriden) {
        ContractStore.update((s) => {
          s.gasPrice = priceFeed.answer
        })
      }
      return priceFeed.answer
    } catch (e) {
      console.error('Error happened fetching fast gas chainlink data:', e)
    }

    return BigNumber.from(0)
  }

  const _fetchEthPriceChainlink = async () => {
    try {
      const priceFeed = await contracts.chainlinkEthAggregator.latestRoundData()
      const ethUsdPrice = parseFloat(
        ethers.utils.formatUnits(priceFeed.answer, 8)
      )
      return ethUsdPrice
    } catch (e) {
      console.error('Error happened fetching eth usd chainlink data:', e)
    }

    return 0
  }

  // Fetches current gas price
  const fetchGasPrice = async () => {
    try {
      const gasPriceRequest = await fetchWithTimeout(
        `https://ethgasstation.info/api/ethgasAPI.json?api-key=${process.env.DEFI_PULSE_API_KEY}`,
        // allow for 5 seconds timeout before falling back to chainlink
        {
          timeout: 5000,
        }
      )

      const gasPrice = BigNumber.from(
        get(await gasPriceRequest.json(), 'average') + '00000000'
      )

      if (!isGasPriceUserOverriden) {
        ContractStore.update((s) => {
          s.gasPrice = gasPrice
        })
      }
      return gasPrice
    } catch (e) {
      console.error(
        `Can not fetch gas prices, using chainlink as fallback method: ${e.message}`
      )
    }

    // fallback to chainlink
    return await _fetchGasPriceChainlink()
  }

  const _calculateSplits = async (sellAmount) => {
    const calculateIt = async () => {
      try {
        const assetAmounts = await contracts.vault.calculateRedeemOutputs(
          ethers.utils.parseUnits(sellAmount.toString(), 18)
        )

        const assets = await Promise.all(
          (
            await contracts.vault.getAllAssets()
          ).map(async (address, index) => {
            const coin = Object.keys(contracts).find(
              (coin) =>
                contracts[coin] &&
                contracts[coin].address.toLowerCase() === address.toLowerCase()
            )

            const amount = ethers.utils.formatUnits(
              assetAmounts[index],
              coinInfoList[coin].decimals
            )

            return {
              coin,
              amount,
            }
          })
        )

        return assets
      } catch (err) {
        console.error(err)
        return {}
      }
    }

    return await calculateIt()
  }

  return {
    estimateSwapSuitabilityFlipper,
    estimateMintSuitabilityVault,
    estimateRedeemSuitabilityVault,
    estimateSwapSuitabilityUniswapV3,
    estimateSwapSuitabilityCurve,
  }
}
Example #16
Source File: useCurrencySwapper.js    From origin-dollar with MIT License 4 votes vote down vote up
useCurrencySwapper = ({
  swapMode,
  inputAmountRaw,
  outputAmount,
  selectedCoin,
  priceToleranceValue,
}) => {
  const [needsApproval, setNeedsApproval] = useState(false)
  const {
    vault: vaultContract,
    ousd: ousdContract,
    usdt: usdtContract,
    usdc: usdcContract,
    dai: daiContract,
    flipper,
    uniV3SwapRouter,
    uniV2Router,
    sushiRouter,
    uniV3SwapQuoter,
    curveRegistryExchange,
    curveOUSDMetaPool,
  } = useStoreState(ContractStore, (s) => s.contracts)
  const curveMetapoolUnderlyingCoins = useStoreState(
    ContractStore,
    (s) => s.curveMetapoolUnderlyingCoins
  )

  const coinInfoList = useStoreState(ContractStore, (s) => s.coinInfoList)

  const allowances = useStoreState(AccountStore, (s) => s.allowances)
  const balances = useStoreState(AccountStore, (s) => s.balances)
  const account = useStoreState(AccountStore, (s) => s.address)
  const connectorName = useStoreState(AccountStore, (s) => s.connectorName)
  const swapEstimations = useStoreState(ContractStore, (s) => s.swapEstimations)
  const swapsLoaded = swapEstimations && typeof swapEstimations === 'object'
  const selectedSwap = useStoreState(ContractStore, (s) => s.selectedSwap)
  const web3react = useWeb3React()
  const { library } = web3react

  const allowancesLoaded =
    typeof allowances === 'object' &&
    allowances.ousd &&
    allowances.usdt &&
    allowances.usdc &&
    allowances.dai

  const connSigner = (contract) => {
    return contract.connect(library.getSigner(account))
  }

  const { contract: coinContract, decimals } =
    coinInfoList[swapMode === 'mint' ? selectedCoin : 'ousd']

  let coinToReceiveDecimals, coinToReceiveContract
  // do not enter conditional body when redeeming a mix
  if (!(swapMode === 'redeem' && selectedCoin === 'mix')) {
    ;({ contract: coinToReceiveContract, decimals: coinToReceiveDecimals } =
      coinInfoList[swapMode === 'redeem' ? selectedCoin : 'ousd'])
  }

  // plain amount as displayed in UI (not in wei format)
  const amount = parseFloat(inputAmountRaw)

  const { swapAmount, minSwapAmount } = calculateSwapAmounts(
    inputAmountRaw,
    decimals,
    priceToleranceValue
  )

  useEffect(() => {
    if (
      !amount ||
      !selectedSwap ||
      !allowances ||
      Object.keys(allowances) === 0
    ) {
      return
    }

    const nameMaps = {
      vault: 'vault',
      flipper: 'flipper',
      uniswap: 'uniswapV3Router',
      uniswapV2: 'uniswapV2Router',
      curve: 'curve',
      sushiswap: 'sushiRouter',
    }

    const coinNeedingApproval = swapMode === 'mint' ? selectedCoin : 'ousd'

    if (coinNeedingApproval === 'ousd' && selectedSwap.name === 'vault') {
      setNeedsApproval(false)
    } else {
      if (nameMaps[selectedSwap.name] === undefined) {
        throw new Error(
          `Can not fetch contract: ${selectedSwap.name} allowance for coin: ${coinNeedingApproval}`
        )
      }

      setNeedsApproval(
        Object.keys(allowances).length > 0 &&
          parseFloat(
            allowances[coinNeedingApproval][nameMaps[selectedSwap.name]]
          ) < amount
          ? selectedSwap.name
          : false
      )
    }
  }, [swapMode, amount, allowances, selectedCoin, selectedSwap])

  const _mintVault = async (
    callObject,
    swapAmount,
    minSwapAmount,
    txParams = {}
  ) => {
    return await callObject.mint(
      coinContract.address,
      swapAmount,
      minSwapAmount,
      txParams
    )
  }

  /* Increases the given gas limit by the specified buffer. BufferToIncrease is expressed
   * in relative percentages. Meaning a 0.2 value will set gasLimit to 120% of the original value
   */
  const increaseGasLimitByBuffer = (gasLimit, bufferToIncrease) => {
    return Math.round(gasLimit * (1 + bufferToIncrease))
  }

  const mintVaultGasEstimate = async (swapAmount, minSwapAmount) => {
    const gasEstimate = (
      await _mintVault(vaultContract.estimateGas, swapAmount, minSwapAmount, {
        from: account,
      })
    ).toNumber()

    return parseInt(
      gasEstimate +
        Math.max(
          mintAbsoluteGasLimitBuffer,
          gasEstimate * mintPercentGasLimitBuffer
        )
    )
  }

  const mintVault = async () => {
    const { minSwapAmount: minSwapAmountReceived } = calculateSwapAmounts(
      outputAmount,
      18,
      priceToleranceValue
    )

    const gasLimit = await mintVaultGasEstimate(
      swapAmount,
      minSwapAmountReceived
    )

    return {
      result: await _mintVault(
        connSigner(vaultContract),
        swapAmount,
        minSwapAmountReceived,
        {
          gasLimit,
        }
      ),
      swapAmount,
      minSwapAmount: minSwapAmountReceived,
    }
  }

  const _redeemVault = async (
    callObject,
    swapAmount,
    minSwapAmount,
    txParams = {}
  ) => {
    const isRedeemAll = Math.abs(swapAmount - balances.ousd) < 1
    if (isRedeemAll) {
      return await callObject.redeemAll(minSwapAmount, txParams)
    } else {
      return await callObject.redeem(swapAmount, minSwapAmount, txParams)
    }
  }

  const redeemVaultGasEstimate = async (swapAmount, minSwapAmount) => {
    return increaseGasLimitByBuffer(
      await _redeemVault(vaultContract.estimateGas, swapAmount, minSwapAmount, {
        from: account,
      }),
      redeemPercentGasLimitBuffer
    )
  }

  const redeemVault = async () => {
    const { minSwapAmount: minSwapAmountReceived } = calculateSwapAmounts(
      outputAmount,
      18,
      priceToleranceValue
    )

    const gasLimit = await redeemVaultGasEstimate(
      swapAmount,
      minSwapAmountReceived
    )

    return {
      result: await _redeemVault(
        connSigner(vaultContract),
        swapAmount,
        minSwapAmountReceived,
        {
          gasLimit,
        }
      ),
      swapAmount,
      minSwapAmount: minSwapAmountReceived,
    }
  }

  const swapFlipper = async () => {
    // need to calculate these again, since Flipper takes all amount inputs in 18 decimal format
    const { swapAmount: swapAmountFlipper } = calculateSwapAmounts(
      inputAmountRaw,
      18
    )

    let flipperResult
    if (swapMode === 'mint') {
      if (selectedCoin === 'dai') {
        flipperResult = await connSigner(flipper).buyOusdWithDai(
          swapAmountFlipper
        )
      } else if (selectedCoin === 'usdt') {
        flipperResult = await connSigner(flipper).buyOusdWithUsdt(
          swapAmountFlipper
        )
      } else if (selectedCoin === 'usdc') {
        flipperResult = await connSigner(flipper).buyOusdWithUsdc(
          swapAmountFlipper
        )
      }
    } else {
      if (selectedCoin === 'dai') {
        flipperResult = await connSigner(flipper).sellOusdForDai(
          swapAmountFlipper
        )
      } else if (selectedCoin === 'usdt') {
        flipperResult = await connSigner(flipper).sellOusdForUsdt(
          swapAmountFlipper
        )
      } else if (selectedCoin === 'usdc') {
        flipperResult = await connSigner(flipper).sellOusdForUsdc(
          swapAmountFlipper
        )
      }
    }

    return {
      result: flipperResult,
      swapAmount,
      minSwapAmount,
    }
  }

  /* Path is an array of strings -> contains all pool pairs enumerated
   * Fees is an array of numbers -> identifying the pool fees of the pairs
   */
  const _encodeUniswapPath = (path, fees) => {
    const FEE_SIZE = 3

    if (path.length != fees.length + 1) {
      throw new Error('path/fee lengths do not match')
    }

    let encoded = '0x'
    for (let i = 0; i < fees.length; i++) {
      // 20 byte encoding of the address
      encoded += path[i].slice(2)
      // 3 byte encoding of the fee
      encoded += fees[i].toString(16).padStart(2 * FEE_SIZE, '0')
    }
    // encode the final token
    encoded += path[path.length - 1].slice(2)

    return encoded.toLowerCase()
  }

  const _encodePath = () => {
    const isMintMode = swapMode === 'mint'
    let path

    if (selectedCoin === 'dai') {
      /* Uniswap now supports 0.01% fees on stablecoin pools
       * TODO: can't get the 0.01% pool to work for DAI even though it is obviously available:
       *
       * - https://info.uniswap.org/#/pools/0x3416cf6c708da44db2624d63ea0aaef7113527c6 -> 0.01%
       */
      if (isMintMode) {
        path = _encodeUniswapPath(
          [daiContract.address, usdtContract.address, ousdContract.address],
          [500, 500]
        )
      } else {
        path = _encodeUniswapPath(
          [ousdContract.address, usdtContract.address, daiContract.address],
          [500, 500]
        )
      }
    } else if (selectedCoin === 'usdc') {
      /* Uniswap now supports 0.01% fees on stablecoin pools
       *
       * - https://info.uniswap.org/#/pools/0x5777d92f208679db4b9778590fa3cab3ac9e2168 -> 0.01%
       */
      if (isMintMode) {
        path = _encodeUniswapPath(
          [usdcContract.address, usdtContract.address, ousdContract.address],
          [100, 500]
        )
      } else {
        path = _encodeUniswapPath(
          [ousdContract.address, usdtContract.address, usdcContract.address],
          [500, 100]
        )
      }
    } else {
      throw new Error(
        `Unexpected uniswap params -> swapMode: ${swapMode} selectedCoin: ${selectedCoin}`
      )
    }

    return path
  }

  const _swapCurve = async (swapAmount, minSwapAmount, isGasEstimate) => {
    const swapParams = [
      curveMetapoolUnderlyingCoins.indexOf(coinContract.address.toLowerCase()),
      curveMetapoolUnderlyingCoins.indexOf(
        coinToReceiveContract.address.toLowerCase()
      ),
      swapAmount,
      minSwapAmount,
    ]

    const gasLimit = increaseGasLimitByBuffer(
      await curveOUSDMetaPool.estimateGas.exchange_underlying(...swapParams, {
        from: account,
      }),
      curveGasLimitBuffer
    )

    if (isGasEstimate) {
      return gasLimit
    } else {
      return await connSigner(curveOUSDMetaPool).exchange_underlying(
        ...swapParams,
        { gasLimit }
      )
    }
  }

  const swapCurveGasEstimate = async (swapAmount, minSwapAmount) => {
    return await _swapCurve(swapAmount, minSwapAmount, true)
  }

  const swapCurve = async () => {
    const { minSwapAmount: minSwapAmountReceived } = calculateSwapAmounts(
      outputAmount,
      coinToReceiveDecimals,
      priceToleranceValue
    )

    return {
      result: await _swapCurve(swapAmount, minSwapAmountReceived, false),
      swapAmount,
      minSwapAmount,
    }
  }

  const quoteCurve = async (swapAmount) => {
    const coinsReceived = await curveRegistryExchange.get_exchange_amount(
      addresses.mainnet.CurveOUSDMetaPool,
      coinContract.address,
      coinToReceiveContract.address,
      swapAmount,
      {
        gasLimit: 1000000,
      }
    )

    return coinsReceived
  }

  const _swapUniswap = async (swapAmount, minSwapAmount, isGasEstimate) => {
    const isMintMode = swapMode === 'mint'

    const swapWithIncreaseGasLimit = async (
      runEstimateFunction,
      runSwapFunction
    ) => {
      const gasLimit = await runEstimateFunction()
      if (isGasEstimate) {
        return gasLimit
      } else {
        return await runSwapFunction({ gasLimit })
      }
    }

    if (selectedCoin === 'usdt') {
      const singleCoinParams = [
        isMintMode ? usdtContract.address : ousdContract.address,
        isMintMode ? ousdContract.address : usdtContract.address,
        500, // pre-defined Factory fee for stablecoins
        account, // recipient
        BigNumber.from(Date.now() + 2 * 60 * 1000), // deadline - 2 minutes from now
        swapAmount, // amountIn
        minSwapAmount, // amountOutMinimum
        0, // sqrtPriceLimitX96
      ]

      const runUsdtGasEstimate = async () => {
        return increaseGasLimitByBuffer(
          (
            await uniV3SwapRouter.estimateGas.exactInputSingle(
              singleCoinParams,
              { from: account }
            )
          ).toNumber(),
          uniswapV3GasLimitBuffer
        )
      }

      return await swapWithIncreaseGasLimit(
        runUsdtGasEstimate,
        async (txParams) => {
          return await connSigner(uniV3SwapRouter).exactInputSingle(
            singleCoinParams,
            txParams
          )
        }
      )
    }

    const path = _encodePath()
    const params = {
      path,
      recipient: account,
      deadline: BigNumber.from(Date.now() + 2 * 60 * 1000), // deadline - 2 minutes from now,
      amountIn: swapAmount,
      amountOutMinimum: minSwapAmount,
    }

    const data = [
      uniV3SwapRouter.interface.encodeFunctionData('exactInput', [params]),
    ]

    const runGasEstimate = async () => {
      return increaseGasLimitByBuffer(
        (
          await uniV3SwapRouter.estimateGas.exactInput(params, {
            from: account,
          })
        ).toNumber(),
        uniswapV3GasLimitBuffer
      )
    }

    return await swapWithIncreaseGasLimit(runGasEstimate, async (txParams) => {
      return await connSigner(uniV3SwapRouter).exactInput(params, txParams)
    })
  }

  const swapUniswapGasEstimate = async (swapAmount, minSwapAmount) => {
    return await _swapUniswap(swapAmount, minSwapAmount, true)
  }

  const swapUniswap = async () => {
    const { minSwapAmount: minSwapAmountReceived } = calculateSwapAmounts(
      outputAmount,
      coinToReceiveDecimals,
      priceToleranceValue
    )

    return {
      result: await _swapUniswap(swapAmount, minSwapAmountReceived, false),
      swapAmount,
      minSwapAmount,
    }
  }

  const quoteUniswap = async (swapAmount) => {
    const isMintMode = swapMode === 'mint'

    if (selectedCoin === 'usdt') {
      return await uniV3SwapQuoter.callStatic.quoteExactInputSingle(
        isMintMode ? usdtContract.address : ousdContract.address,
        isMintMode ? ousdContract.address : usdtContract.address,
        500, // pre-defined Factory fee for stablecoins
        swapAmount,
        0 // sqrtPriceLimitX96
      )
    }

    const path = _encodePath()
    return await uniV3SwapQuoter.callStatic.quoteExactInput(path, swapAmount)
  }

  const _swapUniswapV2 = async (
    swapAmount,
    minSwapAmount,
    isGasEstimate,
    isSushiSwap = false
  ) => {
    const isMintMode = swapMode === 'mint'
    const contract = isSushiSwap ? sushiRouter : uniV2Router
    const swapCallParams = [
      swapAmount, // amountIn
      minSwapAmount, // amountOutMinimum
      getUniV2Path(), // swap path
      account, // recipient
      BigNumber.from(Date.now() + 2 * 60 * 1000), // deadline - 2 minutes from now
    ]

    const txParams = { from: account }
    const gasLimit = increaseGasLimitByBuffer(
      await contract.estimateGas.swapExactTokensForTokens(
        ...swapCallParams,
        txParams
      ),
      isSushiSwap ? sushiswapGasLimitBuffer : uniswapV2GasLimitBuffer
    )

    if (isGasEstimate) {
      return gasLimit
    } else {
      txParams.gasLimit = gasLimit
      return await connSigner(contract).swapExactTokensForTokens(
        ...swapCallParams,
        txParams
      )
    }
  }

  const swapUniswapV2GasEstimate = async (swapAmount, minSwapAmount) => {
    return await _swapUniswapV2(swapAmount, minSwapAmount, true)
  }

  const _swapUniswapV2Variant = async (isSushiSwap = false) => {
    const { minSwapAmount: minSwapAmountReceived } = calculateSwapAmounts(
      outputAmount,
      coinToReceiveDecimals,
      priceToleranceValue
    )

    return {
      result: await _swapUniswapV2(
        swapAmount,
        minSwapAmountReceived,
        false,
        isSushiSwap
      ),
      swapAmount,
      minSwapAmount,
    }
  }

  const swapUniswapV2 = async () => {
    return _swapUniswapV2Variant(false)
  }

  const swapSushiSwap = async () => {
    return _swapUniswapV2Variant(true)
  }

  const swapSushiswapGasEstimate = async (swapAmount, minSwapAmount) => {
    return await _swapUniswapV2(swapAmount, minSwapAmount, true, true)
  }

  const getUniV2Path = () => {
    const isMintMode = swapMode === 'mint'
    let path

    if (selectedCoin === 'dai') {
      if (isMintMode) {
        path = [daiContract.address, usdtContract.address, ousdContract.address]
      } else {
        path = [ousdContract.address, usdtContract.address, daiContract.address]
      }
    } else if (selectedCoin === 'usdc') {
      if (isMintMode) {
        path = [
          usdcContract.address,
          usdtContract.address,
          ousdContract.address,
        ]
      } else {
        path = [
          ousdContract.address,
          usdtContract.address,
          usdcContract.address,
        ]
      }
    } else if (selectedCoin === 'usdt') {
      if (isMintMode) {
        path = [usdtContract.address, ousdContract.address]
      } else {
        path = [ousdContract.address, usdtContract.address]
      }
    } else {
      throw new Error(
        `Unexpected uniswap V2 params -> swapMode: ${swapMode} selectedCoin: ${selectedCoin}`
      )
    }

    return path
  }

  const quoteSushiSwap = async (swapAmount) => {
    return await sushiRouter.getAmountsOut(swapAmount, getUniV2Path())
  }

  const quoteUniswapV2 = async (swapAmount) => {
    return await uniV2Router.getAmountsOut(swapAmount, getUniV2Path())
  }

  return {
    allowancesLoaded,
    needsApproval,
    mintVault,
    mintVaultGasEstimate,
    redeemVault,
    redeemVaultGasEstimate,
    swapFlipper,
    swapUniswapGasEstimate,
    swapUniswap,
    swapUniswapV2GasEstimate,
    swapUniswapV2,
    quoteUniswap,
    quoteUniswapV2,
    quoteSushiSwap,
    swapSushiSwap,
    swapSushiswapGasEstimate,
    quoteCurve,
    swapCurve,
    swapCurveGasEstimate,
  }
}
Example #17
Source File: SettingsDropdown.js    From origin-dollar with MIT License 4 votes vote down vote up
SettingsDropdown = ({ setPriceToleranceValue, priceToleranceValue }) => {
  const [settingsOpen, setSettingsOpen] = useState(false)
  const [showWarning, setShowWarning] = useState()
  const gasPrice = useStoreState(ContractStore, (s) => s.gasPrice)

  useEffect(() => {
    setShowWarning(priceToleranceValue > 1)
  }, [priceToleranceValue])

  return (
    <div className="dropdown-holder">
      <Dropdown
        className="d-flex align-items-center min-h-42"
        content={
          <div className="d-flex flex-column dropdown-menu show">
            <div className="d-flex justify-content-between align-items-center">
              <div className="setting-title">
                {fbt('Price tolerance', 'price tolerance setting')}
              </div>
              <div className="d-flex setting-holder">
                <div className="w-50 d-flex align-items-center">
                  <input
                    value={priceToleranceValue}
                    className="tolerance h-100"
                    onChange={(e) => {
                      e.preventDefault()
                      let value = 0
                      if (!isNaN(e.target.value)) {
                        value = e.target.value
                        setShowWarning(value > 1)
                        value = value > 50 ? 50 : value
                        value = truncateDecimals(value, 2)
                        if (value !== priceToleranceValue) {
                          analytics.track('On price tolerance change', {
                            category: 'settings',
                            label: value,
                          })
                        }
                        setPriceToleranceValue(value)
                      }
                    }}
                  />
                  <div>%</div>
                </div>
                <button
                  className="w-50 d-flex align-items-center justify-content-center auto"
                  onClick={() => {
                    setPriceToleranceValue(0.1)
                    setShowWarning(false)
                  }}
                >
                  AUTO
                </button>
              </div>
            </div>
            <div className={`warning ${showWarning ? '' : 'hide'}`}>
              Your transaction may be frontrun
            </div>
            <div className="d-flex justify-content-between align-items-center margin-top">
              <div className="setting-title">
                {fbt('Gas price', 'Gas price setting')}
              </div>
              <div className="d-flex setting-holder">
                <div className="w-50">
                  <input
                    type="number"
                    value={Math.floor(gasPrice / Math.pow(10, 9))}
                    className="w-100 h-100"
                    onChange={(e) => {
                      let value = e.target.value
                      // ensure positive integers
                      if (value < 0) {
                        value = 0
                      }
                      value = Math.floor(value)
                      value *= Math.pow(10, 9)
                      analytics.track('On gas setting change', {
                        category: 'settings',
                        label: value,
                      })

                      ContractStore.update((s) => {
                        s.gasPrice = BigNumber.from(value)
                        s.isGasPriceUserOverriden = true
                      })
                    }}
                  />
                </div>
                <div className="w-50 d-flex align-items-center justify-content-center gwei">
                  GWEI
                </div>
              </div>
            </div>
          </div>
        }
        open={settingsOpen}
        onClose={() => setSettingsOpen(false)}
      >
        <img
          className="settings-icon"
          src={assetRootPath('/images/settings-icon.svg')}
          onClick={(e) => {
            const newOpenState = !settingsOpen
            setSettingsOpen(newOpenState)
            if (newOpenState) {
              analytics.track('On open settings', {
                category: 'settings',
              })
            }
          }}
        />
      </Dropdown>
      <style jsx>{`
        .dropdown-holder {
          position: absolute;
          top: 15px;
          right: 15px;
        }

        .dropdown-menu {
          top: 115%;
          left: 0;
          right: auto;
          min-width: 290px;
          padding: 18px 18px 18px 20px;
        }

        .settings-icon {
          width: 18px;
          height: 18px;
          cursor: pointer;
        }

        .setting-title {
          font-size: 14px;
          font-weight: bold;
          color: #8293a4;
        }

        .margin-top {
          margin-top: 15px;
        }

        .tolerance {
          width: 70%;
        }

        .setting-holder {
          max-width: 120px;
          min-width: 120px;
          max-height: 40px;
          min-height: 40px;
          border-radius: 5px;
          border: solid 1px #cdd7e0;
          background-color: #f2f3f5;
        }

        input {
          max-width: 60px;
          border: 0px;
          font-size: 14px;
          font-weight: normal;
          color: black;
          text-align: center;
          border-radius: 5px 0 0 5px;
          background-color: #f2f3f5;
        }

        .warning {
          font-size: 14px;
          color: #ff8000;
          margin-top: 10px;
        }

        .warning.hide {
          display: none;
        }

        .gwei {
          font-size: 14px;
          color: #8293a4;
          background-color: white;
          border-radius: 0 5px 5px 0;
          border-left: solid 1px #cdd7e0;
        }

        button.auto {
          font-size: 14px;
          color: white;
          background-color: #1a82ff;
          border-radius: 0 5px 5px 0;
          border: 0;
          border-left: solid 1px #cdd7e0;
        }

        @media (max-width: 799px) {
          .dropdown-holder {
            top: 7px;
            right: 7px;
          }

          .dropdown-menu {
            top: 115%;
            right: 0;
            left: auto;
            padding: 12px 12px 12px 14px;
          }
        }
      `}</style>
    </div>
  )
}
Example #18
Source File: AccountListener.js    From origin-dollar with MIT License 4 votes vote down vote up
AccountListener = (props) => {
  const web3react = useWeb3React()
  const { account, chainId, library, active } = web3react
  const prevAccount = usePrevious(account)
  const prevActive = usePrevious(active)
  const [contracts, setContracts] = useState(null)
  const [cookies, setCookie, removeCookie] = useCookies(['loggedIn'])
  const {
    active: userActive,
    refetchUserData,
    refetchStakingData,
  } = useStoreState(AccountStore, (s) => s)
  const durations = useStoreState(StakeStore, (s) => s.durations)
  const rates = useStoreState(StakeStore, (s) => s.rates)
  const prevRefetchStakingData = usePrevious(refetchStakingData)
  const prevRefetchUserData = usePrevious(refetchUserData)
  const AIR_DROPPED_STAKE_TYPE = 1

  const balancesQuery = useBalancesQuery(account, contracts, {
    onSuccess: (balances) => {
      AccountStore.update((s) => {
        s.balances = balances
      })
    },
  })

  const allowancesQuery = useAllowancesQuery(account, contracts, {
    onSuccess: (allowances) => {
      AccountStore.update((s) => {
        s.allowances = allowances
      })
    },
  })

  const wousdQuery = useWousdQuery(account, contracts, {
    onSuccess: (wousdValue) => {
      AccountStore.update((s) => {
        s.wousdValue = wousdValue
      })
    },
  })

  const apyQuery = useApyQuery({
    onSuccess: (apy) => {
      ContractStore.update((s) => {
        s.apy = apy
      })
    },
  })

  const historyQuery = useTransactionHistoryQuery(account)

  useEffect(() => {
    if ((prevActive && !active) || prevAccount !== account) {
      AccountStore.update((s) => {
        s.allowances = {}
        s.balances = {}
      })
      PoolStore.update((s) => {
        s.claimable_ogn = null
        s.lp_tokens = null
        s.lp_token_allowance = null
        s.staked_lp_tokens = null
        s.your_weekly_rate = null
      })
      StakeStore.update((s) => {
        s.stakes = null
        s.airDropStakeClaimed = false
      })
    }
  }, [active, prevActive, account, prevAccount])

  useEffect(() => {
    const fetchVaultThresholds = async () => {
      if (!contracts) return

      const vault = contracts.vault
      const allocateThreshold = await vault.autoAllocateThreshold()
      const rebaseThreshold = await vault.rebaseThreshold()

      ContractStore.update((s) => {
        s.vaultAllocateThreshold = allocateThreshold
        s.vaultRebaseThreshold = rebaseThreshold
      })
    }

    fetchVaultThresholds()
  }, [contracts])

  const loadData = async (contracts, { onlyStaking } = {}) => {
    if (!account) {
      return
    }
    if (!contracts.ogn.provider) {
      console.warn('Contract provider not yet set')
      return
    }
    if (!contracts) {
      console.warn('Contracts not yet loaded!')
      return
    }
    if (!isCorrectNetwork(chainId)) {
      return
    }

    const { ousd, ogn, ognStakingView } = contracts

    const loadPoolRelatedAccountData = async () => {
      if (!account) return
      if (process.env.ENABLE_LIQUIDITY_MINING !== 'true') return

      const pools = PoolStore.currentState.pools
      const initializedPools = pools.filter((pool) => pool.contract)

      if (pools.length !== initializedPools.length) {
        console.warn(
          'When loading account pool data not all pools have yet initialized'
        )
      }

      // contract needs to be populated?

      // await poolContract.userInfo(account)
      // await displayCurrency(userInfo.amount, lpContract)
      try {
        const additionalPoolData = await Promise.all(
          pools.map(async (pool) => {
            const { lpContract, contract, pool_deposits_bn } = pool
            const additionalData = {
              name: pool.name,
              coin_one: {},
              coin_two: {},
            }

            if (isDevelopment) {
              const token1Contract =
                contracts[pool.coin_one.contract_variable_name]
              const token2Contract =
                contracts[pool.coin_two.contract_variable_name]

              const [
                coin1Allowance,
                coin2Allowance,
                coin1Balance,
                coin2Balance,
              ] = await Promise.all([
                displayCurrency(
                  await token1Contract.allowance(account, lpContract.address),
                  token1Contract
                ),
                displayCurrency(
                  await token2Contract.allowance(account, lpContract.address),
                  token2Contract
                ),
                displayCurrency(
                  await token1Contract.balanceOf(account),
                  token1Contract
                ),
                displayCurrency(
                  await token2Contract.balanceOf(account),
                  token2Contract
                ),
              ])

              additionalData.coin_one.allowance = coin1Allowance
              additionalData.coin_two.allowance = coin2Allowance
              additionalData.coin_one.balance = coin1Balance
              additionalData.coin_two.balance = coin2Balance
              additionalData.coin_one.contract = token1Contract
              additionalData.coin_two.contract = token2Contract
            }

            const [
              userInfo,
              unclaimedOgn,
              lp_tokens,
              lp_token_allowance,
              rewardPerBlockBn,
              userReward,
              poolDepositsBn,
            ] = await Promise.all([
              await contract.userInfo(account),
              displayCurrency(await contract.pendingRewards(account), ogn),
              displayCurrency(await lpContract.balanceOf(account), lpContract),
              displayCurrency(
                await lpContract.allowance(account, contract.address),
                lpContract
              ),
              await contract.rewardPerBlock(),
              displayCurrency(await contract.pendingRewards(account), ogn),
              await lpContract.balanceOf(contract.address),
            ])

            const userTokensStaked = await displayCurrency(
              userInfo.amount,
              lpContract
            )

            additionalData.claimable_ogn = unclaimedOgn
            additionalData.lp_tokens = lp_tokens
            additionalData.lp_token_allowance = lp_token_allowance
            additionalData.staked_lp_tokens = userTokensStaked
            additionalData.pool_deposits = await displayCurrency(
              poolDepositsBn,
              lpContract
            )
            additionalData.reward_per_block = await displayCurrency(
              rewardPerBlockBn,
              ogn
            )

            const userTokensStakedNumber = Number(userTokensStaked)
            /* userTokensStaked / total pool deposits = the share of the pool a user owns
             * Multiplied by rewards per block times number of blocks in a week
             */
            additionalData.your_weekly_rate =
              userTokensStakedNumber === 0 || poolDepositsBn.isZero()
                ? 0
                : await displayCurrency(
                    userInfo.amount
                      /* in dev environment sometimes users can have more tokens staked than total pool token staked.
                       * that happens when user balance updates before the pool balance.
                       */
                      .div(poolDepositsBn)
                      .mul(rewardPerBlockBn)
                      .mul(BigNumber.from(6500 * 7)), // blocks in a day times 7 days in a week
                    ogn
                  )
            return additionalData
          })
        )

        const enrichedPools = PoolStore.currentState.pools.map((pool) => {
          const additionalData = additionalPoolData.filter(
            (apool) => apool.name === pool.name
          )[0]
          const merged = {
            ...pool,
            ...additionalData,
            coin_one: {
              ...pool.coin_one,
              ...additionalData.coin_one,
            },
            coin_two: {
              ...pool.coin_two,
              ...additionalData.coin_two,
            },
          }

          return merged
        })
        //console.log('Enriched pools', enrichedPools)
        PoolStore.update((s) => {
          s.pools = enrichedPools
        })
      } catch (e) {
        console.error(
          'AccountListener.js error - can not load account specific data for pools',
          e
        )
      }
    }

    const loadStakingRelatedData = async () => {
      if (!account) return

      try {
        /* OgnStakingView is used here instead of ognStaking because the first uses the jsonRpcProvider and
         * the latter the wallet one. Sometime these are not completely in sync and while the first one might
         * report a transaction already mined, the second one not yet.
         *
         * We use jsonRpcProvider to wait for transactions to be mined, so using the samne provider to fetch the
         * staking data solves the out of sync problem.
         */
        const [stakes, airDropStakeClaimed] = await Promise.all([
          ognStakingView.getAllStakes(account),
          ognStakingView.airDroppedStakeClaimed(
            account,
            AIR_DROPPED_STAKE_TYPE
          ),
        ])

        const decoratedStakes = stakes
          ? decorateContractStakeInfoWithTxHashes(stakes)
          : []

        StakeStore.update((s) => {
          s.stakes = decoratedStakes
          s.airDropStakeClaimed = airDropStakeClaimed
        })
      } catch (e) {
        console.error(
          'AccountListener.js error - can not load staking related data: ',
          e
        )
      }
    }

    const loadRebaseStatus = async () => {
      if (!account) return
      // TODO handle other contract types. We only detect Gnosis Safe as having
      // opted out here as rebaseState will always be 0 for all EOAs
      const isSafe = !!_.get(library, 'provider.safe.safeAddress', false)
      AccountStore.update((s) => {
        s.isSafe = isSafe
      })
      const rebaseOptInState = await ousd.rebaseState(account)
      AccountStore.update((s) => {
        s.rebaseOptedOut = isSafe && rebaseOptInState === 0
      })
    }

    if (onlyStaking) {
      await loadStakingRelatedData()
    } else {
      balancesQuery.refetch()
      allowancesQuery.refetch()
      wousdQuery.refetch()

      await Promise.all([
        loadRebaseStatus(),
        // TODO maybe do this if only in the LM part of the dapp since it is very heavy
        loadPoolRelatedAccountData(),
        loadStakingRelatedData(),
      ])
    }
  }

  useEffect(() => {
    if (account) {
      login(account, setCookie)
      historyQuery.refetch()
    }

    const loadLifetimeEarnings = async () => {
      if (!account) return

      const response = await fetch(
        `${
          process.env.ANALYTICS_ENDPOINT
        }/api/v1/address/${account.toLowerCase()}/yield`
      )

      if (response !== undefined && response.ok) {
        const lifetimeYield = (await response.json()).lifetime_yield
        AccountStore.update((s) => {
          s.lifetimeYield = lifetimeYield
        })
      }
    }

    const setupContractsAndLoad = async () => {
      /* If we have a web3 provider present and is signed into the allowed network:
       * - NODE_ENV === production -> mainnet
       * - NODE_ENV === development -> localhost, forknet
       * then we use that chainId to setup contracts.
       *
       * In other case we still want to have read only capability of the contracts with a general provider
       * so we can fetch `getAPR` from Vault for example to use on marketing pages even when the user is not
       * logged in with a web3 provider.
       *
       */
      let usedChainId, usedLibrary
      if (chainId && isCorrectNetwork(chainId)) {
        usedChainId = chainId
        usedLibrary = library
      } else {
        usedChainId = parseInt(process.env.ETHEREUM_RPC_CHAIN_ID)
        usedLibrary = null
      }

      window.fetchId = window.fetchId ? window.fetchId : 0
      window.fetchId += 1
      apyQuery.refetch()

      const contracts = await setupContracts(
        account,
        usedLibrary,
        usedChainId,
        window.fetchId
      )
      setContracts(contracts)

      setTimeout(() => {
        loadData(contracts)
      }, 1)
    }

    setupContractsAndLoad()
    loadLifetimeEarnings()
  }, [account, chainId])

  useEffect(() => {
    // trigger a force refetch user data when the flag is set by a user
    if (
      (contracts && isCorrectNetwork(chainId),
      refetchUserData && !prevRefetchUserData)
    ) {
      loadData(contracts)
    }
    AccountStore.update((s) => {
      s.refetchUserData = false
    })
  }, [userActive, contracts, refetchUserData, prevRefetchUserData])

  useEffect(() => {
    // trigger a force refetch user data when the flag is set by a user
    if (
      (contracts && isCorrectNetwork(chainId),
      refetchStakingData && !prevRefetchStakingData)
    ) {
      loadData(contracts, { onlyStaking: true })
    }
    AccountStore.update((s) => {
      s.refetchStakingData = false
    })
  }, [userActive, contracts, refetchStakingData, prevRefetchStakingData])

  useEffect(() => {
    let balancesInterval
    if (contracts && userActive === 'active' && isCorrectNetwork(chainId)) {
      loadData(contracts)

      balancesInterval = setInterval(() => {
        loadData(contracts)
      }, 7000)
    }

    return () => {
      if (balancesInterval) {
        clearInterval(balancesInterval)
      }
    }
  }, [userActive, contracts])

  return ''
}
Example #19
Source File: dashboard.js    From origin-dollar with MIT License 4 votes vote down vote up
Dashboard = ({ locale, onLocale }) => {
  const allowances = useStoreState(AccountStore, (s) => s.allowances)
  const balances = useStoreState(AccountStore, (s) => s.balances)
  const pools = useStoreState(PoolStore, (s) => s.pools)

  const account = useStoreState(AccountStore, (s) => s.address)
  const { chainId } = useWeb3React()

  const {
    vault,
    usdt,
    dai,
    tusd,
    usdc,
    ousd,
    viewVault,
    ogn,
    uniV2OusdUsdt,
    liquidityOusdUsdt,
    ognStaking,
    compensation,
    uniV3OusdUsdt,
    uniV3DaiUsdt,
    uniV3UsdcUsdt,
    uniV3NonfungiblePositionManager,
    uniV3SwapRouter,
    flipper,
  } = useStoreState(ContractStore, (s) => s.contracts || {})
  const isMainnetFork = process.env.NODE_ENV === 'development' && chainId === 1
  const isGovernor = account && account === governorAddress
  const [refreshFlipperData, setRefreshFlipperData] = useState(0)
  const [refreshUniV3Data, setRefreshUniV3Data] = useState(0)
  const [flipperData, setFlipperData] = useState({})
  const [uniV3Data, setUniV3Data] = useState({})
  const [adjusterLocked, setAdjusterLocked] = useState(null)
  const [compensationTotalClaims, setCompensationTotalClaims] =
    useState('Loading...')

  const updateAdjuster = async () => {
    setAdjusterLocked(await compensation.isAdjusterLocked())
  }

  const loadTotalClaims = async () => {
    setCompensationTotalClaims(
      await displayCurrency(await compensation.totalClaims(), ousd)
    )
  }

  useEffect(() => {
    if (compensation && compensation.provider) {
      updateAdjuster()
      loadTotalClaims()
    }
  }, [compensation])

  useEffect(() => {
    const refreshDataInterval = setInterval(() => {
      setRefreshFlipperData(refreshFlipperData + Math.random())
      setRefreshUniV3Data(refreshUniV3Data + Math.random())
    }, 4000)

    return () => {
      clearInterval(refreshDataInterval)
    }
  }, [])

  useEffect(() => {
    if (
      !(
        !dai ||
        !dai.provider ||
        !usdc ||
        !usdc.provider ||
        !usdt ||
        !usdt.provider ||
        !ousd ||
        !ousd.provider
      )
    ) {
      const refreshBalances = async () => {
        const daiAmount = await dai.balanceOf(flipper.address)
        const usdtAmount = await usdt.balanceOf(flipper.address)
        const usdcAmount = await usdc.balanceOf(flipper.address)
        const ousdAmount = await ousd.balanceOf(flipper.address)

        const daiAllowance = await displayCurrency(
          await dai.allowance(account, flipper.address),
          dai
        )
        const usdtAllowance = await displayCurrency(
          await usdt.allowance(account, flipper.address),
          usdt
        )
        const usdcAllowance = await displayCurrency(
          await usdc.allowance(account, flipper.address),
          usdc
        )
        const ousdAllowance = await displayCurrency(
          await ousd.allowance(account, flipper.address),
          ousd
        )

        setFlipperData({
          daiBalance: daiAmount,
          usdtBalance: usdtAmount,
          usdcBalance: usdcAmount,
          ousdBalance: ousdAmount,
          daiAllowance: daiAllowance,
          usdtAllowance: usdtAllowance,
          usdcAllowance: usdcAllowance,
          ousdAllowance: ousdAllowance,
        })
      }

      refreshBalances()
    }
  }, [refreshFlipperData, dai, usdc, usdt, ousd])

  useEffect(() => {
    if (
      !(
        !usdt ||
        !usdt.provider ||
        !ousd ||
        !ousd.provider ||
        !uniV3SwapRouter ||
        !uniV3SwapRouter.provider
      )
    ) {
      let usdtAllowanceManager,
        ousdAllowanceManager,
        daiAllowanceManager,
        usdcAllowanceManager = 'Loading'
      const refreshUniswapData = async () => {
        const usdtAllowanceRouter = await displayCurrency(
          await usdt.allowance(account, uniV3SwapRouter.address),
          usdt
        )
        const ousdAllowanceRouter = await displayCurrency(
          await ousd.allowance(account, uniV3SwapRouter.address),
          ousd
        )

        const daiAllowanceRouter = await displayCurrency(
          await dai.allowance(account, uniV3SwapRouter.address),
          usdt
        )
        const usdcAllowanceRouter = await displayCurrency(
          await usdc.allowance(account, uniV3SwapRouter.address),
          ousd
        )

        if (!isProduction) {
          usdtAllowanceManager = await displayCurrency(
            await usdt.allowance(
              account,
              uniV3NonfungiblePositionManager.address
            ),
            usdt
          )
          ousdAllowanceManager = await displayCurrency(
            await ousd.allowance(
              account,
              uniV3NonfungiblePositionManager.address
            ),
            ousd
          )
          daiAllowanceManager = await displayCurrency(
            await dai.allowance(
              account,
              uniV3NonfungiblePositionManager.address
            ),
            dai
          )
          usdcAllowanceManager = await displayCurrency(
            await usdc.allowance(
              account,
              uniV3NonfungiblePositionManager.address
            ),
            usdc
          )
        }

        const usdtBalancePoolousd_usdt = await displayCurrency(
          await usdt.balanceOf(uniV3OusdUsdt.address),
          usdt
        )
        const ousdBalancePoolousd_usdt = await displayCurrency(
          await ousd.balanceOf(uniV3OusdUsdt.address),
          ousd
        )

        const usdtBalancePooldai_usdt = await displayCurrency(
          await usdt.balanceOf(uniV3DaiUsdt.address),
          usdt
        )
        const daiBalancePooldai_usdt = await displayCurrency(
          await dai.balanceOf(uniV3DaiUsdt.address),
          dai
        )

        const usdtBalancePoolusdc_usdt = await displayCurrency(
          await usdt.balanceOf(uniV3UsdcUsdt.address),
          usdt
        )
        const usdcBalancePoolusdc_usdt = await displayCurrency(
          await usdc.balanceOf(uniV3UsdcUsdt.address),
          usdc
        )

        setUniV3Data({
          usdtAllowanceRouter,
          ousdAllowanceRouter,
          daiAllowanceRouter,
          usdcAllowanceRouter,
          usdtAllowanceManager,
          ousdAllowanceManager,
          daiAllowanceManager,
          usdcAllowanceManager,
          usdtBalancePoolousd_usdt,
          ousdBalancePoolousd_usdt,
          usdtBalancePooldai_usdt,
          daiBalancePooldai_usdt,
          usdtBalancePoolusdc_usdt,
          usdcBalancePoolusdc_usdt,
        })
      }

      refreshUniswapData()
    }
  }, [
    refreshUniV3Data,
    usdt,
    ousd,
    uniV3SwapRouter,
    uniV3NonfungiblePositionManager,
  ])

  const randomAmount = (multiple = 0) => {
    return String(
      Math.floor(Math.random() * (999999999 * multiple)) / 100000 + 1000
    )
  }

  const mintByCommandLineOption = () => {
    if (isMainnetFork) {
      alert(
        "To grant stable coins go to project's 'contracts' folder and run 'yarn run grant-stable-coins:fork' "
      )
    }
  }

  const notSupportedOption = () => {
    if (isMainnetFork) {
      alert("Not supported when running main net fork -> 'yarn run node:fork'")
    }
  }

  const clearAllAllowances = async () => {
    notSupportedOption()

    await usdt.decreaseAllowance(
      vault.address,
      ethers.utils.parseUnits(allowances['usdt'].vault, await usdt.decimals())
    )

    await dai.decreaseAllowance(
      vault.address,
      ethers.utils.parseUnits(allowances['dai'].vault, await dai.decimals())
    )

    await usdc.decreaseAllowance(
      vault.address,
      ethers.utils.parseUnits(allowances['usdc'].vault, await usdc.decimals())
    )

    await ousd.decreaseAllowance(
      vault.address,
      ethers.utils.parseUnits(allowances['ousd'].vault, await ousd.decimals())
    )
  }

  const sendOUSDToContract = async () => {
    await ousd.transfer(
      compensation.address,
      ethers.utils.parseUnits('20000000', await ousd.decimals())
    )
  }

  const startClaimPeriod = async (seconds) => {
    await compensation.start(seconds)
  }

  const setAdjusterLock = async (lock) => {
    if (lock) {
      await compensation.lockAdjuster()
    } else {
      await compensation.unlockAdjuster()
    }
    await updateAdjuster()
  }

  const mintUSDT = async (multiple) => {
    mintByCommandLineOption()
    await usdt.mint(
      ethers.utils.parseUnits(randomAmount(multiple), await usdt.decimals())
    )
  }

  const sendCoinToFlipper = async (coinContract, amount) => {
    await coinContract.transfer(
      flipper.address,
      ethers.utils.parseUnits(amount.toString(), await coinContract.decimals())
    )

    setRefreshFlipperData(refreshFlipperData + 1)
  }

  const approveFlipper = async (coinContract) => {
    await coinContract.approve(flipper.address, ethers.constants.MaxUint256)
  }

  const swapFlipperUsdtToOusd = async (bnAmount) => {
    await flipper.buyOusdWithUsdt(bnAmount)
  }

  const mintOGN = async (multiple) => {
    mintByCommandLineOption()
    await ogn.mint(
      ethers.utils.parseUnits(randomAmount(multiple), await ogn.decimals())
    )
  }

  const sendOGNToStakingContract = async () => {
    await ogn.transfer(
      ognStaking.address,
      ethers.utils.parseUnits('1000000', await ogn.decimals())
    )
  }

  const approveStakingToMoveOgn = async () => {
    notSupportedOption()
    await ogn.approve(ognStaking.address, ethers.constants.MaxUint256)
  }

  const approveUSDT = async () => {
    notSupportedOption()
    await usdt.approve(vault.address, ethers.constants.MaxUint256)
  }

  const mintDAI = async (multiple) => {
    mintByCommandLineOption()
    await dai.mint(
      ethers.utils.parseUnits(randomAmount(multiple), await dai.decimals())
    )
  }

  const approveDAI = async () => {
    notSupportedOption()
    await dai.approve(vault.address, ethers.constants.MaxUint256)
  }

  const mintUSDC = async (multiple) => {
    mintByCommandLineOption()
    await usdc.mint(
      ethers.utils.parseUnits(randomAmount(multiple), await usdc.decimals())
    )
  }

  const approveUSDC = async () => {
    notSupportedOption()
    await usdc.approve(vault.address, ethers.constants.MaxUint256)
  }

  // const mintTUSD = async (amount) => {
  //   mintByCommandLineOption()
  //   await tusd.mint(
  //     ethers.utils.parseUnits(amount || randomAmount(), await tusd.decimals())
  //   )
  // }

  // const approveTUSD = async () => {
  //   notSupportedOption()
  //   await tusd.approve(
  //     vault.address,
  //     ethers.constants.MaxUint256
  //   )
  // }

  const buyOUSD = async () => {
    await ousd.mint(
      usdt.address,
      ethers.utils.parseUnits('100.0', await usdt.decimals())
    )
  }

  const depositYield = async () => {
    notSupportedOption()
    await ousd.depositYield(
      usdt.address,
      ethers.utils.parseUnits('10.0', await usdt.decimals())
    )
  }

  const unPauseDeposits = async () => {
    notSupportedOption()
    await vault.unpauseDeposits()
  }

  const approveOUSD = async () => {
    notSupportedOption()
    await ousd.approve(vault.address, ethers.constants.MaxUint256)
  }

  const redeemOutputs = async () => {
    const result = await vault.calculateRedeemOutputs(
      ethers.utils.parseUnits('10', await ousd.decimals())
    )

    console.log(result)
  }

  const redeemDAI = async () => {
    await vault.redeemAll(dai.address)
  }

  const redeemUSDT = async () => {
    await vault.redeemAll(usdt.address)
  }

  const redeemUSDC = async () => {
    await vault.redeemAll(usdc.address)
  }

  const setRedeemFee = async (amount) => {
    await vault.setRedeemFeeBps(ethers.utils.parseUnits(amount.toString(), 0))
  }

  const approveUSDTForUniswapOUSD_USDT = async () => {
    notSupportedOption()
    await usdt.approve(uniV2OusdUsdt.address, ethers.constants.MaxUint256)
  }

  const approveOUSDForUniswapOUSD_USDT = async () => {
    notSupportedOption()
    await ousd.approve(uniV2OusdUsdt.address, ethers.constants.MaxUint256)
  }

  const approveForUniswapV3Router = async (coinContract) => {
    notSupportedOption()
    await coinContract.approve(
      uniV3SwapRouter.address,
      ethers.constants.MaxUint256
    )

    setRefreshUniV3Data(refreshUniV3Data + 1)
  }

  const approveForUniswapV3Manager = async (coinContract) => {
    notSupportedOption()
    await coinContract.approve(
      uniV3NonfungiblePositionManager.address,
      ethers.constants.MaxUint256
    )

    setRefreshUniV3Data(refreshUniV3Data + 1)
  }

  const initializeUniswapV3 = async (contract1, contract2, poolContract) => {
    const sqrtPriceX96 = encodePriceSqrt(
      ethers.utils.parseUnits('1', await contract1.decimals()),
      ethers.utils.parseUnits('1', await contract2.decimals())
    )

    // TODO: This isn't initialized correctly so prices are off, but the transactions still execute

    // console.log('PRICE: ', sqrtPriceX96.toString())
    // the sqrtPriceX96 taken directly from pool creation on mainnet: https://etherscan.io/tx/0xe83eb25244b0e3a5b040f824ac9983cff0bc610747df45bf57755ef7b4bc3c74
    // await uniV3OusdUsdt.initialize(BigNumber.from('79224306130848112672356'))

    await poolContract.initialize(sqrtPriceX96)
  }

  const provideLiquidityV3 = async (contract1, contract2) => {
    // Below part done directly by this periphery contract:
    // https://github.com/Uniswap/uniswap-v3-periphery/blob/9ca9575d09b0b8d985cc4d9a0f689f7a4470ecb7/contracts/base/LiquidityManagement.sol#L80-L86

    // If error 'LOK' is thrown then the pool might have not been initialized
    const result = await uniV3NonfungiblePositionManager.mint([
      contract1.address,
      contract2.address,
      500, // pre-defined Factory fee for stablecoins
      20, // tick lower
      50, // tick upper
      ethers.utils.parseUnits('1000', await contract1.decimals()), // amount0Desired
      ethers.utils.parseUnits('1000', await contract2.decimals()), // amount1Desired
      //ethers.utils.parseUnits('900', 18), // amount0Min
      0,
      0,
      account, // recipient
      BigNumber.from(Date.now() + 10000), // deadline - 10 seconds from now
    ])
  }

  const testUniV3Swap100Coin1to2 = async (contract1, contract2) => {
    // If error 'LOK' is thrown then the pool might have not been initialized
    await uniV3SwapRouter.exactInputSingle([
      contract1.address,
      contract2.address,
      500, // pre-defined Factory fee for stablecoins
      account, // recipient
      BigNumber.from(Date.now() + 10000), // deadline - 10 seconds from now
      ethers.utils.parseUnits('100', await contract1.decimals()), // amountIn
      //ethers.utils.parseUnits('98', await usdt.decimals()), // amountOutMinimum
      0, // amountOutMinimum
      0, // sqrtPriceLimitX96
    ])
  }

  const testUniV3Swap100Coin2to1 = async (contract1, contract2) => {
    // If error 'LOK' is thrown then the pool might have not been initialized
    await uniV3SwapRouter.exactInputSingle([
      contract2.address,
      contract1.address,
      500, // pre-defined Factory fee for stablecoins
      account, // recipient
      BigNumber.from(Date.now() + 2 * 60 * 1000), // deadline - 2 minutes from now
      ethers.utils.parseUnits('100', await contract2.decimals()), // amountIn
      //ethers.utils.parseUnits('98', await usdt.decimals()), // amountOutMinimum
      0, // amountOutMinimum
      0, // sqrtPriceLimitX96
    ])
  }

  const setupSupportAssets = async () => {
    notSupportedOption()
    await vault.supportAsset(dai.address, 'DAI')

    await vault.supportAsset(usdt.address, 'USDT')

    await vault.supportAsset(usdc.address, 'USDC')
  }

  const tableRows = () => {
    return [...Object.keys(currencies), 'ousd'].map((x) => {
      const name = x.toUpperCase()
      const balance = get(balances, x)
      const allowance = Number(get(allowances, `${x}.vault`))
      const unlimited = allowance && allowance > Number.MAX_SAFE_INTEGER

      return (
        <tr key={x}>
          <td>{name}</td>
          <td>{unlimited ? 'Unlimited' : allowance ? 'Some' : 'None'}</td>
          <td>1</td>
          <td>{formatCurrency(balance)}</td>
          <td>{unlimited ? 'Max' : formatCurrency(allowance)}</td>
        </tr>
      )
    })
  }

  const displayPoolInfo = (
    coin1,
    coin2,
    coin1Contract,
    coin2Contract,
    poolContract
  ) => {
    const poolName = coin1.toUpperCase() + '/' + coin2.toUpperCase()
    return (
      <div>
        <h3>{poolName}</h3>
        <table className="table table-bordered">
          <thead>
            <tr>
              <td>Asset</td>
              <td>Router - Allowance</td>
              <td>Liquidity Manger - Allowance </td>
              <td>{poolName} Pool - Balance</td>
            </tr>
          </thead>
          <tbody>
            {[coin1, coin2].map((coin) => {
              const name = coin.toUpperCase()
              const coinToDecimals = {
                usdt: 6,
                usdc: 6,
                dai: 18,
                ousd: 18,
              }
              const allowanceRouter = uniV3Data[`${coin}AllowanceRouter`]
              const allowanceManager = uniV3Data[`${coin}AllowanceManager`]
              const poolBalance =
                uniV3Data[`${coin}BalancePool${coin1}_${coin2}`]

              return (
                <tr key={name}>
                  <td>{name}</td>
                  <td>
                    {allowanceRouter
                      ? allowanceRouter === '0.0'
                        ? allowanceRouter
                        : 'Max'
                      : 'Loading'}
                  </td>
                  <td>
                    {allowanceManager
                      ? allowanceManager === '0.0'
                        ? allowanceManager
                        : 'Max'
                      : 'Loading'}
                  </td>
                  <td>{poolBalance}</td>
                </tr>
              )
            })}
          </tbody>
        </table>
        <div className="d-flex flex-wrap">
          <div
            className="btn btn-primary my-4 mr-3"
            onClick={() =>
              initializeUniswapV3(coin1Contract, coin2Contract, poolContract)
            }
          >
            Initialize Pool
          </div>
          <div
            className="btn btn-primary my-4 mr-3"
            onClick={() => provideLiquidityV3(coin1Contract, coin2Contract)}
          >
            Provide Liquidity
          </div>
          <div
            className="btn btn-primary my-4 mr-3"
            onClick={() =>
              testUniV3Swap100Coin1to2(coin1Contract, coin2Contract)
            }
          >
            Test Uniswap 100 {coin1}
          </div>
          <div
            className="btn btn-primary my-4 mr-3"
            onClick={() =>
              testUniV3Swap100Coin2to1(coin1Contract, coin2Contract)
            }
          >
            Test Uniswap 100 {coin2}
          </div>
        </div>
      </div>
    )
  }

  return (
    <>
      <Layout locale={locale} onLocale={onLocale} dapp>
        <Nav dapp locale={locale} onLocale={onLocale} />
        <div className="my-5">
          {!account && <h1 className="text-white">No account :(</h1>}
          {account && (
            <>
              <h1>Balances</h1>
              <div className="card w25 mb-4">
                <div className="card-body">
                  <h5 className="card-title">Current Balance</h5>
                  <p className="card-text">
                    {formatCurrency(get(balances, 'ousd'))} OUSD
                  </p>
                </div>
              </div>
              <table className="table table-bordered">
                <thead>
                  <tr>
                    <td>Asset</td>
                    <td>Permission</td>
                    <td>Exchange Rate</td>
                    <td>Your Balance</td>
                    <td>Allowance</td>
                  </tr>
                </thead>
                <tbody>{tableRows()}</tbody>
              </table>
              <div className="d-flex flex-wrap">
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDT()}
                >
                  Mint 1,000 USDT
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDT(1)}
                >
                  Mint random USDT
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDT(10000)}
                >
                  Mint hella USDT
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={approveUSDT}
                >
                  Approve USDT
                </div>
                <div className="btn btn-primary my-4 mr-3" onClick={redeemUSDT}>
                  Redeem USDT
                </div>
              </div>
              <div className="d-flex flex-wrap">
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintDAI()}
                >
                  Mint 1,000 DAI
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintDAI(1)}
                >
                  Mint random DAI
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintDAI(10000)}
                >
                  Mint hella DAI
                </div>
                <div className="btn btn-primary my-4 mr-3" onClick={approveDAI}>
                  Approve DAI
                </div>
                <div className="btn btn-primary my-4 mr-3" onClick={redeemDAI}>
                  Redeem DAI
                </div>
              </div>
              <div className="d-flex flex-wrap">
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDC()}
                >
                  Mint 1,000 USDC
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDC(1)}
                >
                  Mint random USDC
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintUSDC(10000)}
                >
                  Mint hella USDC
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={approveUSDC}
                >
                  Approve USDC
                </div>
                <div className="btn btn-primary my-4 mr-3" onClick={redeemUSDC}>
                  Redeem USDC
                </div>
              </div>
              {/*
            <div className="d-flex flex-wrap">
              <div className="btn btn-primary my-4 mr-3" onClick={() => mintTUSD()}>
                Mint TUSD
              </div>
              <div className="btn btn-primary my-4 mr-3" onClick={approveTUSD}>
                Approve TUSD
              </div>
            </div>
            */}
              <div className="d-flex flex-wrap">
                {isGovernor && (
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={depositYield}
                  >
                    Deposit $10 Yield
                  </div>
                )}
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={clearAllAllowances}
                >
                  Clear All Allowances
                </div>
                <div className="btn btn-primary my-4 mr-3" onClick={buyOUSD}>
                  Buy OUSD
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={unPauseDeposits}
                >
                  Un-Pause Deposits
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={approveOUSD}
                >
                  Approve OUSD
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={setupSupportAssets}
                >
                  Support DAI & USDT & USDC
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={redeemOutputs}
                >
                  Calculate Redeem outputs
                </div>
              </div>

              <h1 className="mt-5">Staking</h1>
              <table className="table table-bordered">
                <thead>
                  <tr>
                    <td>OGN balance</td>
                    <td>{formatCurrency(get(balances, 'ogn'))}</td>
                  </tr>
                </thead>
              </table>
              <div className="d-flex flex-wrap">
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => mintOGN(10000)}
                >
                  Mint hella OGN
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => sendOGNToStakingContract()}
                >
                  Supply staking contract with OGN
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveStakingToMoveOgn()}
                >
                  Approve staking contract to move OGN
                </div>
              </div>

              <h1 className="mt-5">Compensation</h1>
              <div>
                Is contract adjuster locked:{' '}
                <b>
                  {adjusterLocked === null
                    ? 'Loading'
                    : adjusterLocked.toString()}
                </b>
              </div>
              <div>Total claims in the contract: {compensationTotalClaims}</div>
              <div>
                Below actions can only be started using a governor account. To
                get that account see the mnemonic in harhat.config.js and fetch
                the first account
              </div>
              <h1 className="mt-5">Flipper</h1>
              <div>
                <div className="mb-2">Balance of coins on Flipper contract</div>
                <table className="table table-bordered">
                  <thead>
                    <tr>
                      <td>Asset</td>
                      <td>Balance</td>
                      <td>Allowance</td>
                    </tr>
                  </thead>
                  <tbody>
                    {[...Object.keys(currencies), 'ousd'].map((coin) => {
                      const name = coin.toUpperCase()

                      const coinToDecimals = {
                        usdt: 6,
                        dai: 18,
                        ousd: 18,
                        usdc: 6,
                      }

                      const flipperBalance = flipperData[`${coin}Balance`]
                      const flipperAllowance = flipperData[`${coin}Allowance`]
                      return (
                        <tr key={name}>
                          <td>{name}</td>
                          <td>
                            {flipperBalance
                              ? formatCurrency(
                                  ethers.utils.formatUnits(
                                    flipperBalance,
                                    coinToDecimals[coin]
                                  )
                                )
                              : 'Loading'}
                          </td>
                          <td>
                            {flipperAllowance
                              ? flipperAllowance === '0.0'
                                ? flipperAllowance
                                : 'Max'
                              : 'Loading'}
                          </td>
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
                <div>
                  Make sure you have stablecoin funds available on your wallet
                  before transfering
                  <div className="d-flex flex-wrap">
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(usdt, 1000)}
                    >
                      Fund with 1,000 USDT
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(usdt, 100000)}
                    >
                      Fund with 100,000 USDT
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(dai, 1000)}
                    >
                      Fund with 1,000 DAI
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(dai, 100000)}
                    >
                      Fund with 100,000 DAI
                    </div>
                  </div>
                  <div className="d-flex flex-wrap">
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(usdc, 1000)}
                    >
                      Fund with 1,000 USDC
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(usdc, 100000)}
                    >
                      Fund with 100,000 USDC
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(ousd, 1000)}
                    >
                      Fund with 1,000 OUSD
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => sendCoinToFlipper(ousd, 100000)}
                    >
                      Fund with 100,000 OUSD
                    </div>
                  </div>
                  <div className="d-flex flex-wrap">
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => approveFlipper(ousd)}
                    >
                      Approve OUSD
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => approveFlipper(usdt)}
                    >
                      Approve USDT
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => approveFlipper(usdc)}
                    >
                      Approve USDC
                    </div>
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() => approveFlipper(dai)}
                    >
                      Approve DAI
                    </div>
                    {/* Flipper uses amounts denominated in 1e18 */}
                    <div
                      className="btn btn-primary my-4 mr-3"
                      onClick={() =>
                        swapFlipperUsdtToOusd(ethers.utils.parseUnits('1', 18))
                      }
                    >
                      Swap 1 USDT for OUSD
                    </div>
                  </div>
                </div>
              </div>
              <h1 className="mt-5">Uniswap V3</h1>
              <h3 className="mt-5">
                Router and Liquidity manager general actions:
              </h3>
              <div className="d-flex flex-wrap">
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Router(usdt)}
                >
                  Approve USDT Router
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Router(ousd)}
                >
                  Approve OUSD Router
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Router(usdc)}
                >
                  Approve USDC Router
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Router(dai)}
                >
                  Approve DAI Router
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Manager(usdt)}
                >
                  Approve USDT Manager
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Manager(ousd)}
                >
                  Approve OUSD Manager
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Manager(usdc)}
                >
                  Approve USDC Manager
                </div>
                <div
                  className="btn btn-primary my-4 mr-3"
                  onClick={() => approveForUniswapV3Manager(dai)}
                >
                  Approve DAI Manager
                </div>
              </div>
              {displayPoolInfo('ousd', 'usdt', ousd, usdt, uniV3OusdUsdt)}
              {displayPoolInfo('dai', 'usdt', dai, usdt, uniV3DaiUsdt)}
              {displayPoolInfo('usdc', 'usdt', usdc, usdt, uniV3UsdcUsdt)}
              {!isProduction && (
                <>
                  <h1 className="mt-5">Utils</h1>
                  <div>
                    <div className="d-flex flex-wrap">
                      <div
                        className="btn btn-primary my-4 mr-3"
                        onClick={() => setRedeemFee(50)}
                      >
                        Set redeemFee on Vault to 0.5%
                      </div>
                    </div>
                  </div>
                </>
              )}
            </>
          )}
        </div>
        <h1 className="mt-5">Liquidity mining</h1>
        {isProduction && (
          <h2>
            Pool debug information not available in production environment
          </h2>
        )}
        {!isProduction &&
          pools &&
          pools.map((pool) => {
            const lp_token_allowance = Number(pool.lp_token_allowance)
            const lp_token_allowance_unlimited =
              lp_token_allowance && lp_token_allowance > Number.MAX_SAFE_INTEGER

            return (
              <div key={pool.name}>
                <h2 className="mt-5">{pool.name} pool</h2>
                <table className="table table-bordered">
                  <thead>
                    <tr>
                      <td>Pool stablecoin</td>
                      <td>Balance</td>
                      <td>Allowance</td>
                    </tr>
                  </thead>
                  <tbody>
                    {[pool.coin_one, pool.coin_two].map((coin) => {
                      const name = coin.name.toUpperCase()
                      const balance = Number(coin.balance)
                      const allowance = Number(coin.allowance)
                      const unlimited =
                        allowance && allowance > Number.MAX_SAFE_INTEGER

                      return (
                        <tr key={name}>
                          <td>{name}</td>
                          <td>{formatCurrency(balance)}</td>
                          <td>
                            {unlimited ? 'Max' : formatCurrency(allowance)}
                          </td>
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
                <div className="d-flex flex-wrap">
                  {
                    <div
                      className="btn btn-primary my-4 mr-3"
                      disabled={pool.coin_one.name === 'OUSD'}
                      onClick={async () => {
                        if (pool.coin_one.name === 'OUSD') {
                          return
                        }

                        await pool.coin_one.contract.mint(
                          ethers.utils.parseUnits(
                            randomAmount(100000),
                            await pool.coin_one.contract.decimals()
                          )
                        )
                      }}
                    >
                      {pool.coin_one.name !== 'OUSD' && (
                        <>Mint Bazillion {pool.coin_one.name}</>
                      )}
                      {pool.coin_one.name === 'OUSD' && (
                        <>Mint OUSD from the dapp</>
                      )}
                    </div>
                  }
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.coin_two.contract.mint(
                        ethers.utils.parseUnits(
                          randomAmount(100000),
                          await pool.coin_two.contract.decimals()
                        )
                      )
                    }}
                  >
                    Mint Bazillion {pool.coin_two.name}
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.coin_one.contract.approve(
                        pool.lpContract.address,
                        ethers.constants.MaxUint256
                      )
                    }}
                  >
                    Approve {pool.coin_one.name}
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.coin_two.contract.approve(
                        pool.lpContract.address,
                        ethers.constants.MaxUint256
                      )
                    }}
                  >
                    Approve {pool.coin_two.name}
                  </div>
                </div>
                <table className="table table-bordered">
                  <thead>
                    <tr>
                      <td>Token name</td>
                      <td>user's LP token Balance</td>
                      <td>Pool allowance (of LP token)</td>
                      <td>Staked tokens</td>
                      <td>Unclaimed OGN</td>
                      <td>Your weekly rate</td>
                      <td>Total pool deposits</td>
                      <td>Pool reward per block</td>
                    </tr>
                  </thead>
                  <tbody>
                    <tr>
                      <td>{pool.name}</td>
                      <td>{formatCurrency(pool.lp_tokens)}</td>
                      <td>
                        {lp_token_allowance_unlimited
                          ? 'Max'
                          : formatCurrency(lp_token_allowance)}
                      </td>
                      <td>{formatCurrency(pool.staked_lp_tokens)}</td>
                      <td>{formatCurrency(pool.claimable_ogn)}</td>
                      <td>{formatCurrency(pool.your_weekly_rate)}</td>
                      <td>{formatCurrency(pool.pool_deposits)}</td>
                      <td>{formatCurrency(pool.reward_per_block)}</td>
                    </tr>
                  </tbody>
                </table>
                <div className="d-flex flex-wrap">
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.lpContract.mint(
                        ethers.utils.parseUnits(
                          '1000.0',
                          await pool.lpContract.decimals()
                        )
                      )
                    }}
                  >
                    Mint LP token
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.lpContract.approve(
                        pool.contract.address,
                        ethers.constants.MaxUint256
                      )
                    }}
                  >
                    Approve LP token (for pool)
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.lpContract.decreaseAllowance(
                        pool.contract.address,
                        ethers.utils.parseUnits(
                          pool.lp_token_allowance,
                          await pool.lpContract.decimals()
                        )
                      )
                    }}
                  >
                    Clear LP token allowance (for pool)
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.contract.deposit(
                        ethers.utils.parseUnits(
                          '51.0',
                          await pool.lpContract.decimals()
                        )
                      )
                    }}
                  >
                    Stake some LP tokens
                  </div>
                  <div
                    className="btn btn-primary my-4 mr-3"
                    onClick={async () => {
                      await pool.contract.claim()
                    }}
                  >
                    Claim OGN
                  </div>
                </div>
              </div>
            )
          })}
      </Layout>
      <style jsx>{`
        .home {
          padding-top: 80px;
        }

        table {
          background-color: white;
        }

        @media (max-width: 799px) {
          .home {
            padding: 0;
          }
        }
      `}</style>
    </>
  )
}
Example #20
Source File: index.js    From Artion-Client with GNU General Public License v3.0 4 votes vote down vote up
PaintBoard = () => {
  const dispatch = useDispatch();
  const history = useHistory();

  const {
    explorerUrl,
    apiUrl,
    fetchMintableCollections,
    getNonce,
    addUnlockableContent,
    checkBan,
  } = useApi();

  const { registerRoyalty } = useSalesContract();
  const { loadContract } = useContract();

  const { account, chainId } = useWeb3React();

  const imageRef = useRef();

  const [selected, setSelected] = useState([]);
  const [collections, setCollections] = useState([]);
  const [nft, setNft] = useState();
  const [type, setType] = useState();
  const [image, setImage] = useState(null);
  const [fee, setFee] = useState(null);

  const [name, setName] = useState('');
  const [symbol, setSymbol] = useState('');
  const [description, setDescription] = useState('');
  const [royalty, setRoyalty] = useState('');
  const [xtra, setXtra] = useState('');
  const [supply, setSupply] = useState(0);
  const [hasUnlockableContent, setHasUnlockableContent] = useState(false);
  const [unlockableContent, setUnlockableContent] = useState('');

  const [currentMintingStep, setCurrentMintingStep] = useState(0);
  const [isMinting, setIsMinting] = useState(false);

  const [lastMintedTnxId, setLastMintedTnxId] = useState('');

  const authToken = useSelector(state => state.ConnectWallet.authToken);

  const getFee = async () => {
    setFee(null);

    try {
      const contract = await loadContract(nft, FEE_ABI);
      const _fee = await contract.platformFee();
      setFee(parseFloat(_fee.toString()) / 10 ** 18);
    } catch {
      setFee(0);
    }
  };

  const getCollections = async () => {
    try {
      const { data } = await fetchMintableCollections(authToken);
      setCollections(data);
      if (data.length) {
        setSelected([data[0]]);
      }
    } catch (err) {
      console.log(err);
    }
  };

  useEffect(() => {
    if (authToken) {
      getCollections();
    }
  }, [authToken]);

  useEffect(() => {
    if (!nft) return;

    getFee();
  }, [nft]);

  useEffect(() => {
    dispatch(HeaderActions.toggleSearchbar(true));
  }, []);

  const onDrop = useCallback(acceptedFiles => {
    setImage(acceptedFiles[0]);
  }, []);

  const { getRootProps, getInputProps } = useDropzone({
    accept: accept.join(', '),
    multiple: false,
    onDrop,
    maxSize: 15728640,
  });

  const removeImage = () => {
    setImage(null);
    if (imageRef.current) {
      imageRef.current.value = '';
    }
  };

  const imageToBase64 = () => {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.readAsDataURL(image);
      reader.onload = () => {
        resolve(reader.result);
      };
      reader.onerror = err => {
        reject(err);
      };
    });
  };

  const validateMetadata = () => {
    return name !== '' && account !== '' && image;
  };

  const resetMintingStatus = () => {
    setTimeout(() => {
      setIsMinting(false);
      setCurrentMintingStep(0);
    }, 1000);
  };

  const mintNFT = async () => {
    if (!account) {
      showToast('info', 'Connect your wallet first');
      return;
    }
    if (chainId !== ChainId.FANTOM && chainId !== ChainId.FANTOM_TESTNET) {
      showToast('info', 'You are not connected to Fantom Opera Network');
      return;
    }
    const balance = await WalletUtils.checkBalance(account);

    if (balance < fee) {
      showToast(
        'custom',
        `Your balance should be at least ${fee} FTM to mint an NFT`
      );
      return;
    }

    let isBanned = await checkBan(account, authToken);

    if (isBanned) {
      showToast('error', 'You are banned from minting');
      return;
    }

    setLastMintedTnxId('');
    // show stepper
    setIsMinting(true);
    console.log('created from ', account);
    if (!validateMetadata()) {
      resetMintingStatus();
      return;
    }

    let signature;
    let addr;

    if (hasUnlockableContent && unlockableContent.length > 0) {
      const { data: nonce } = await getNonce(account, authToken);
      try {
        const signer = await getSigner();
        const msg = `Approve Signature on Artion.io with nonce ${nonce}`;
        signature = await signer.signMessage(msg);
        addr = ethers.utils.verifyMessage(msg, signature);
      } catch (err) {
        showToast(
          'error',
          'You need to sign the message to be able to update account settings.'
        );
        resetMintingStatus();
        return;
      }
    }

    let formData = new FormData();
    const base64 = await imageToBase64();
    formData.append('image', base64);
    formData.append('name', name);
    formData.append('account', account);
    formData.append('description', description);
    formData.append('symbol', symbol);
    formData.append('xtra', xtra);
    const _royalty = parseInt(royalty) * 100;
    formData.append('royalty', isNaN(_royalty) ? 0 : _royalty);
    try {
      let result = await axios({
        method: 'post',
        url: `${apiUrl}/ipfs/uploadImage2Server`,
        data: formData,
        headers: {
          'Content-Type': 'multipart/form-data',
          Authorization: 'Bearer ' + authToken,
        },
      });

      console.log('upload image result is ');

      const jsonHash = result.data.jsonHash;

      const contract = await loadContract(
        nft,
        type === 721 ? SINGLE_NFT_ABI : MULTI_NFT_ABI
      );
      try {
        const args =
          type === 721 ? [account, jsonHash] : [account, supply, jsonHash];

        let tx;

        if (!fee) {
          tx = await contract.mint(...args);
        } else {
          const options = {
            value: ethers.utils.parseEther(fee.toString()),
            gasPrice: getHigherGWEI(),
          };
          const gasEstimate = await contract.estimateGas.mint(...args, options);
          options.gasLimit = calculateGasMargin(gasEstimate);
          tx = await contract.mint(...args, options);
        }
        setCurrentMintingStep(1);
        setLastMintedTnxId(tx.hash);

        setCurrentMintingStep(2);
        const confirmedTnx = await tx.wait();
        setCurrentMintingStep(3);
        let mintedTkId;
        if (type === 721) {
          const evtCaught = confirmedTnx.logs[0].topics;
          mintedTkId = BigNumber.from(evtCaught[3]);
        } else {
          mintedTkId = BigNumber.from(
            ethers.utils.hexDataSlice(confirmedTnx.logs[1].data, 0, 32)
          );
        }

        const royaltyTx = await registerRoyalty(
          nft,
          mintedTkId.toNumber(),
          isNaN(_royalty) ? 0 : _royalty
        );
        await royaltyTx.wait();

        // save unlockable content
        if (hasUnlockableContent && unlockableContent.length > 0) {
          await addUnlockableContent(
            nft,
            mintedTkId.toNumber(),
            unlockableContent,
            signature,
            addr,
            authToken
          );
        }

        showToast('success', 'New NFT item minted!');
        removeImage();
        setName('');
        setSymbol('');
        setDescription('');

        setTimeout(() => {
          history.push(`/explore/${nft}/${mintedTkId.toNumber()}`);
        }, 1000 + Math.random() * 2000);
      } catch (error) {
        showToast('error', formatError(error));
      }
    } catch (error) {
      showToast('error', error.message);
    }
    resetMintingStatus();
  };

  return (
    <div className={styles.container}>
      <Header border />
      <div className={styles.body}>
        <div className={styles.board}>
          <div {...getRootProps({ className: styles.uploadCont })}>
            <input {...getInputProps()} ref={imageRef} />
            {image ? (
              <>
                <img
                  className={styles.image}
                  src={URL.createObjectURL(image)}
                />
                <div className={styles.overlay}>
                  <CloseIcon className={styles.remove} onClick={removeImage} />
                </div>
              </>
            ) : (
              <>
                <div className={styles.uploadtitle}>
                  Drop files here or&nbsp;
                  <span
                    className={styles.browse}
                    onClick={() => imageRef.current?.click()}
                  >
                    browse
                  </span>
                </div>
                <div className={styles.uploadsubtitle}>
                  JPG, PNG, BMP, GIF Max 15mb.
                </div>
              </>
            )}
          </div>
        </div>
        <div className={styles.panel}>
          <div className={styles.panelInputs}>
            <div className={styles.panelLeft}>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Collection</p>
                <Select
                  options={collections}
                  disabled={isMinting}
                  values={selected}
                  onChange={([col]) => {
                    setSelected([col]);
                    setNft(col.erc721Address);
                    setType(col.type);
                  }}
                  className={styles.select}
                  placeholder="Choose Collection"
                  itemRenderer={({ item, methods }) => (
                    <div
                      key={item.erc721Address}
                      className={styles.collection}
                      onClick={() => {
                        methods.clearAll();
                        methods.addItem(item);
                      }}
                    >
                      <img
                        src={`https://cloudflare-ipfs.com/ipfs/${item.logoImageHash}`}
                        className={styles.collectionLogo}
                      />
                      <div className={styles.collectionName}>
                        {item.collectionName}
                      </div>
                    </div>
                  )}
                  contentRenderer={({ props: { values } }) =>
                    values.length > 0 ? (
                      <div className={styles.collection}>
                        <img
                          src={`https://cloudflare-ipfs.com/ipfs/${values[0].logoImageHash}`}
                          className={styles.collectionLogo}
                        />
                        <div className={styles.collectionName}>
                          {values[0].collectionName}
                        </div>
                      </div>
                    ) : (
                      <div className={styles.collection} />
                    )
                  }
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Name</p>
                <input
                  className={styles.formInput}
                  maxLength={40}
                  placeholder="Name"
                  value={name}
                  onChange={e => setName(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>{name.length}/40</div>
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Symbol</p>
                <input
                  className={styles.formInput}
                  maxLength={20}
                  placeholder="Symbol"
                  value={symbol}
                  onChange={e => setSymbol(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>{symbol.length}/20</div>
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>Description</p>
                <textarea
                  className={cx(styles.formInput, styles.longInput)}
                  maxLength={120}
                  placeholder="Description"
                  value={description}
                  onChange={e => setDescription(e.target.value)}
                  disabled={isMinting}
                />
                <div className={styles.lengthIndicator}>
                  {description.length}/120
                </div>
              </div>
            </div>
            <div className={styles.panelRight}>
              {type === 1155 && (
                <div className={styles.formGroup}>
                  <p className={styles.formLabel}>Supply</p>
                  <PriceInput
                    className={styles.formInput}
                    placeholder="Supply"
                    decimals={0}
                    value={'' + supply}
                    onChange={setSupply}
                    disabled={isMinting}
                  />
                </div>
              )}
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Royalty (%)&nbsp;
                  <BootstrapTooltip
                    title="If you set a royalty here, you will get X percent of sales price each time an NFT is sold on our platform."
                    placement="top"
                  >
                    <HelpOutlineIcon />
                  </BootstrapTooltip>
                </p>
                <PriceInput
                  className={styles.formInput}
                  placeholder="Royalty"
                  decimals={2}
                  value={'' + royalty}
                  onChange={val =>
                    val[val.length - 1] === '.'
                      ? setRoyalty(val)
                      : setRoyalty(Math.min(100, +val))
                  }
                  disabled={isMinting}
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Link to IP Rights Document (Optional)&nbsp;
                  <BootstrapTooltip
                    title="Link to the document which proves your ownership of this image."
                    placement="top"
                  >
                    <HelpOutlineIcon />
                  </BootstrapTooltip>
                </p>
                <input
                  className={styles.formInput}
                  placeholder="Enter Link"
                  value={xtra}
                  onChange={e => setXtra(e.target.value)}
                  disabled={isMinting}
                />
              </div>
              <div className={styles.formGroup}>
                <p className={styles.formLabel}>
                  Unlockable Content&nbsp;
                  <PurpleSwitch
                    checked={hasUnlockableContent}
                    onChange={e => {
                      setHasUnlockableContent(e.target.checked);
                      setUnlockableContent('');
                    }}
                    name="unlockableContent"
                  />
                </p>
                {hasUnlockableContent && (
                  <textarea
                    className={cx(styles.formInput, styles.longInput)}
                    maxLength={500}
                    placeholder="Unlockable Content"
                    value={unlockableContent}
                    onChange={e => setUnlockableContent(e.target.value)}
                    disabled={isMinting}
                  />
                )}
              </div>
            </div>
          </div>

          {isMinting && (
            <div>
              <Stepper activeStep={currentMintingStep} alternativeLabel>
                {mintSteps.map(label => (
                  <Step key={label}>
                    <StepLabel>{label}</StepLabel>
                  </Step>
                ))}
              </Stepper>
            </div>
          )}
          <div
            className={cx(
              styles.button,
              (isMinting || !account || !validateMetadata()) && styles.disabled
            )}
            onClick={
              isMinting || !account || !validateMetadata() ? null : mintNFT
            }
          >
            {isMinting ? (
              <ClipLoader size="16" color="white"></ClipLoader>
            ) : (
              'Mint'
            )}
          </div>
          <div className={styles.fee}>
            {fee !== null ? (
              <>
                <InfoIcon />
                &nbsp;{fee} FTM are charged to create a new NFT.
              </>
            ) : (
              <Skeleton width={330} height={22} />
            )}
          </div>
          <div className={styles.mintStatusContainer}>
            {lastMintedTnxId !== '' && (
              <a
                className={styles.tnxAnchor}
                target="_blank"
                rel="noopener noreferrer"
                href={`${explorerUrl}/tx/${lastMintedTnxId}`}
              >
                You can track the last transaction here ...
              </a>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}
Example #21
Source File: preDungeonCheck.js    From ethernal with MIT License 4 votes vote down vote up
store = derived(
  wallet,
  async ($wallet, set) => {
    const _set = obj => {
      $data = { ...$data, ...obj };
      console.log('pre dungeon check', $data);
      set($data);
    };

    if ($wallet.status === 'Ready') {
      if (lastWalletAddress !== $wallet.address) {
        lastWalletAddress = $wallet.address;
        _set({ status: 'Loading' });
        const delegateAccount = getDelegateKey($wallet.address);

        const checkCharacter = async () => {
          const characterId = await wallet.call('Player', 'getLastCharacterId', $wallet.address);
          const isDelegateReady = await wallet.call(
            'Player',
            'isDelegateFor',
            delegateAccount.address,
            $wallet.address,
          );
          const result = await wallet.call('Characters', 'fullOwnerOf', characterId);
          const isCharacterInDungeon =
            result.owner === wallet.getContract('Dungeon').address &&
            result.subOwner.eq(BigNumber.from($wallet.address));
          const balance = await wallet.getProvider().getBalance($wallet.address);
          // TODO should be free
          const insufficientBalance = balance.lt('1100000000000000000');
          return { characterId, isDelegateReady, isCharacterInDungeon, insufficientBalance };
        };

        const { characterId, isDelegateReady, isCharacterInDungeon, insufficientBalance } = await checkCharacter();

        let characterInfo;
        const { minBalance } = config($wallet.chainId);
        let refill = minBalance;
        try {
          characterInfo = await fetchCache(`characters/${characterId}`);
        } catch (e) {
          console.log('failed to fetch character info from cache');
        }

        let ressurectedId;
        if (characterInfo && !isCharacterInDungeon && characterInfo.status.status === 'dead') {
          const { Dungeon } = window.contracts; // TODO get contract elsewhere
          const topic = Dungeon.interface.getEventTopic(Dungeon.interface.events['Resurrect(uint256,uint256)']);
          const [ressurect] = await Dungeon.queryFilter({
            address: Dungeon.address,
            topics: [topic, uint256(characterId)],
          });
          if (ressurect) {
            ressurectedId = ressurect.args.newCharacterId;
          }
        }

        _set({
          status: 'Done',
          isDelegateReady,
          isCharacterInDungeon,
          characterId,
          characterInfo,
          ressurectedId,
          refill,
          insufficientBalance,
        });

        if (isCharacterInDungeon) {
          preDungeon.clear();
          characterChoice.clear();
        }

        store.checkBackIn = async value => {
          const gasEstimate = 4000000; // @TODO: proper estimation
          _set({ status: 'SigningBackIn', delegateAccount });
          let tx;
          try {
            tx = await wallet.tx(
              { gas: gasEstimate + 15000, gasPrice, value },
              'Player',
              'addDelegate',
              delegateAccount.address,
            );
            await tx.wait();
          } catch (e) {
            _set({ status: 'Error', error: { code: 'addDelegate', message: e.toString(), e, wallet } }); // TODO
          }
          const { isDelegateReady, isCharacterInDungeon, insufficientBalance } = await checkCharacter();
          _set({
            status: 'Done',
            isDelegateReady,
            isCharacterInDungeon,
            insufficientBalance,
          });
        };

        store.enter = async ({ ressurectedId, characterInfo }) => {
          const { location } = await fetchCache('entry');
          await wallet
            .tx(
              { gas: BigNumber.from(2000000).toHexString(), gasPrice },
              'Player',
              'enter',
              '0x0000000000000000000000000000000000000000',
              ressurectedId,
              '0',
              characterInfo.characterName,
              '0',
              location || coordinatesToLocation('0,0'),
            )
            .then(tx => tx.wait());
          const { isDelegateReady, isCharacterInDungeon, insufficientBalance } = await checkCharacter();
          _set({
            status: 'Done',
            isDelegateReady,
            isCharacterInDungeon,
            insufficientBalance,
          });
        };

        store.join = async ({ name, characterClass }) => {
          _set({ status: 'Joining' });
          const gasEstimate = BigNumber.from(2000000).toHexString();
          const { price } = config($wallet.chainId);
          const value = BigNumber.from(price).toHexString();

          const { location } = await fetchCache('entry');

          const tx = await wallet.tx(
            { gas: gasEstimate, gasPrice, value },
            'Player',
            'createAndEnter',
            delegateAccount.address,
            0,
            name,
            characterClass,
            location || coordinatesToLocation('0,0'),
          );
          const receipt = await tx.wait();
          console.log({ receipt });
          console.log('gas used for join', BigNumber.from(receipt.gasUsed).toString());

          const { isCharacterInDungeon, isDelegateReady } = await checkCharacter();

          if (isCharacterInDungeon) {
            preDungeon.clear();
            characterChoice.clear();
          }
          _set({
            firstTime: true,
            status: 'Done',
            isDelegateReady,
            isCharacterInDungeon,
          });
        };
      }
    } else {
      lastWalletAddress = null;
      _set({ status: 'None' });
    }
  },
  $data,
)
Example #22
Source File: claim.js    From ethernal with MIT License 4 votes vote down vote up
store = derived(
  wallet,
  async ($wallet, set) => {
    const _set = obj => {
      $claim = { ...$claim, ...obj };
      log.info('CLAIM', JSON.stringify($claim, null, '  '));
      set($claim);
    };

    const gasPrice = BigNumber.from('1000000000'); // await provider.getGasPrice();
    const gasLimit = BigNumber.from(21000);
    const gasFee = gasLimit.mul(gasPrice);
    const extraValue = BigNumber.from('100000000000000');
    const minimum = gasFee.add(extraValue);
    const maximum = BigNumber.from('4000000000000000000'); // @TODO: config)

    if (claimKey && typeof $claim.rawBalance === 'undefined') {
      try {
        claimWallet = new Wallet(claimKey);
        const provider = wallet.getFallbackProvider();
        if (provider) {
          (async () => {
            let claimBalance = await wallet.getFallbackProvider().getBalance(claimWallet.address);
            if (claimBalance.lt(minimum)) {
              claimBalance = BigNumber.from(0);
            }
            if (claimBalance.gt(maximum)) {
              claimBalance = maximum;
            }
            // eslint-disable-next-line no-console
            console.log({
              address: claimWallet.address,
              status: 'WaitingWallet',
              rawBalance: claimBalance,
              balance: utils.formatUnits(claimBalance, 18),
            });
            _set({
              status: 'WaitingWallet',
              rawBalance: claimBalance,
              balance: utils.formatUnits(claimBalance, 18),
            });
          })();
        }
      } catch (e) {
        const claimBalance = BigNumber.from(0);
        _set({
          status: 'WaitingWallet',
          rawBalance: claimBalance,
          balance: utils.formatUnits(claimBalance, 18),
        });
      }
    }

    async function claim() {
      _set({ status: 'Loading' });
      const provider = wallet.getProvider();

      let claimingTxHash;
      const localStorageKeyForClaimTxHash = `${$wallet.address}_${$wallet.chainId}_claimTxHash`;
      try {
        claimingTxHash = localStorage.getItem(localStorageKeyForClaimTxHash);
      } catch (err) {
        //
      }

      if (claimingTxHash && claimingTxHash !== '') {
        _set({ status: 'WaitingOldTx' });

        const tx = await provider.getTransaction(claimingTxHash);
        if (tx) {
          const receipt = await tx.wait();
          if (tx.blockNumber) {
            if (receipt.status === 1) {
              _set({ status: 'Claimed' });
              clearClaimKey();
              return;
            }
            _set({ status: 'Failed' });
          } else {
            const txReceipt = await tx.wait();
            if (txReceipt.status === 1) {
              _set({ status: 'Claimed' });
              clearClaimKey();
              return;
            }
            _set({ status: 'Failed' });
          }
        } else {
          log.trace(`cannot find tx ${claimingTxHash}`);
        }
      }

      const claimBalance = await provider.getBalance(claimWallet.address);
      log.trace({ claimBalance });

      const claimValue = BigNumber.from('5000000000000000000'); // @TODO: from Config 5 DAI
      if (claimBalance.gte(minimum)) {
        const signer = claimWallet.connect(provider);
        let value = claimBalance.sub(gasFee);
        const maxValue = BigNumber.from(claimValue);
        if (value.gt(maxValue)) {
          value = maxValue;
        }
        _set({ status: 'Claiming' });

        const tx = await signer.sendTransaction({
          to: $wallet.address,
          value,
          gasLimit,
          gasPrice,
        });
        localStorage.setItem(localStorageKeyForClaimTxHash, tx.hash);
        _set({ status: 'WaitingTx' });

        const receipt = await tx.wait();
        if (receipt.status === 1) {
          _set({ status: 'Claimed' });
          clearClaimKey();
          return;
        }
        _set({ status: 'Failed' });
      } else {
        _set({ status: 'Gone' });
      }
      clearClaimKey();
    }

    store.claim = claim;
    store.acknowledge = () => {
      _set({ status: 'None' });
    };
  },
  $claim,
)