OasisDEX/oasis-react

View on GitHub
src/store/reducers/wrapUnwrap.js

Summary

Maintainability
A
2 hrs
Test Coverage
C
71%
import { createAction, handleActions } from "redux-actions";
import { fromJS } from "immutable";
import { reset, formValueSelector, change } from "redux-form/immutable";

import {
  DEFAULT_GAS_PRICE,
  ETH_UNIT_ETHER,
  TOKEN_ETHER,
  TOKEN_GOLEM,
  TOKEN_WRAPPED_ETH,
  TOKEN_WRAPPED_GNT
} from "../../constants";
import wrapUnwrap from "../selectors/wrapUnwrap";
import accounts from "../selectors/accounts";
import { fulfilled } from "../../utils/store";
import web3 from "../../bootstrap/web3";
import {
  TX_UNWRAP_ETHER,
  TX_UNWRAP_TOKEN_WRAPPER,
  TX_WRAP_ETHER,
  TX_WRAP_TOKEN_WRAPPER
} from "./transactions";
import { createPromiseActions } from "../../utils/createPromiseActions";
import balances from "../selectors/balances";
import { handleTransaction } from "../../utils/transactions/handleTransaction";
import {
  getDepositBrokerContractInstance,
  getTokenContractInstance,
  getTokenNoProxyContractInstance,
  initDepositBrokerContract
} from "../../bootstrap/contracts";

export const WRAP_UNWRAP_CREATE_DEPOSIT_BROKER =
  "WRAP_UNWRAP/CREATE_DEPOSIT_BROKER";
export const WRAP_UNWRAP_CLEAR_DEPOSIT_BROKER =
  "WRAP_UNWRAP/CLEAR_DEPOSIT_BROKER";

const initialState = fromJS({
  wrapperTokenPairs: [
    {
      unwrapped: TOKEN_ETHER,
      wrapper: TOKEN_WRAPPED_ETH
    },
    // {
    //   unwrapped: TOKEN_GOLEM,
    //   wrapper: TOKEN_WRAPPED_GNT
    // }
  ],
  activeUnwrappedToken: TOKEN_ETHER,
  loadedBrokerContracts: [],
  brokerAddresses: {},
  activeTokenWrapStatus: null,
  activeTokenUnwrapStatus: null
});

export const WRAP_ETHER = "WRAP_UNWRAP/WRAP_ETHER";
export const UNWRAP_ETHER = "WRAP_UNWRAP/UNWRAP_ETHER";
export const WRAP_TOKEN_WRAPPER_NEXT_TRANSACTION_DELAY_MS = 3000;

export const WRAP_TOKEN_WRAPPER = "WRAP_UNWRAP/WRAP_GNT_TOKEN";
export const UNWRAP_TOKEN_WRAPPER = "WRAP_UNWRAP/UNWRAP_GNT_TOKEN";

export const TOKEN_WRAP_STATUS_AWAITING_TRANSFER_TO_BROKER_APPROVAL =
  "TOKEN_WRAP_STATUS_AWAITING_TRANSFER_TO_BROKER_APPROVAL";
export const TOKEN_WRAP_STATUS_TRANSFER_TO_BROKER_PENDING =
  "TOKEN_WRAP_STATUS_TRANSFER_TO_BROKER_PENDING";
export const TOKEN_WRAP_STATUS_TRANSFER_TO_BROKER_COMPLETE =
  "TOKEN_WRAP_STATUS_TRANSFER_TO_BROKER_COMPLETE";
export const TOKEN_WRAP_STATUS_AWAITING_TRANSFER_TO_WRAPPER_CONTRACT_APROVAL =
  "TOKEN_WRAP_STATUS_AWAITING_TRANSFER_TO_WRAPPER_CONTRACT_APROVAL";
export const TOKEN_WRAP_STATUS_WRAP_COMPLETE =
  "TOKEN_WRAP_STATUS_WRAP_COMPLETE";

export const TOKEN_UNWRAP_STATUS_AWAITING_UNWRAP_APPROVAL =
  "TOKEN_UNWRAP_STATUS_AWAITING_UNWRAP_APPROVAL";
export const TOKEN_UNWRAP_STATUS_UNWRAP_PENDING =
  "TOKEN_UNWRAP_STATUS_UNWRAP_PENDING";
export const TOKEN_UNWRAP_STATUS_UNWRAP_COMPLETE =
  "TOKEN_UNWRAP_STATUS_UNWRAP_COMPLETE";

const getWrapAmount = (rootState, wrapType) =>
  web3.toWei(
    formValueSelector(
      wrapType === WRAP_ETHER ? "wrapEther" : "wrapTokenWrapper"
    )(rootState, "amount"),
    ETH_UNIT_ETHER
  );

const getUnwrapAmount = (rootState, unwrapToken) =>
  web3.toWei(
    formValueSelector(
      unwrapToken === UNWRAP_ETHER ? "unwrapEther" : "unwrapTokenWrapper"
    )(rootState, "amount"),
    ETH_UNIT_ETHER
  );

const setActiveWrapUnwrappedToken = createAction(
  "WRAP_UNWRAP/SET_ACTIVE_UNWRAPPED_TOKEN",
  token => token
);

const loadGNTBrokerAddress = createAction(
  "WRAP_UNWRAP/LOAD_GNT_TOKEN_ADDRESS",
  address =>
    new Promise((resolve, reject) =>
      getTokenNoProxyContractInstance(TOKEN_WRAPPED_GNT).getBroker.call(
        address,
        (e, address) => {
          if (e) {
            reject(e);
          } else {
            resolve(address);
          }
        }
      )
    ),
  address => address
);
const loadGNTBrokerAddressEpic = () => async (dispatch, getState) =>
  dispatch(loadGNTBrokerAddress(accounts.defaultAccount(getState()))).then(
    ({ value }) => value
  );

const createGNTDepositBroker = createAction(
  "WRAP_UNWRAP/CREATE_DEPOSIT_BROKER",
  ({ gasPrice = DEFAULT_GAS_PRICE } = {}) =>
    getTokenContractInstance(TOKEN_WRAPPED_GNT).createBroker({
      gasPrice
    })
);
const createDepositBrokerEpic = (
  tokenName,
  withCallbacks,
  nextTransaction,
  {
    doAddTransactionEpic = null,
    doCreateGNTDepositBroker = createGNTDepositBroker,
    nextTransactionDelay = WRAP_TOKEN_WRAPPER_NEXT_TRANSACTION_DELAY_MS
  } = {}
) => dispatch => {
  switch (tokenName) {
    case TOKEN_GOLEM:
      return handleTransaction(
        {
          dispatch,
          callsNext: true,
          transactionDispatcher: () => dispatch(doCreateGNTDepositBroker()),
          transactionType: TX_WRAP_TOKEN_WRAPPER,
          txMeta: {
            txSubType: WRAP_UNWRAP_CREATE_DEPOSIT_BROKER
          },
          withCallbacks,
          onCallNextTransaction: nextTransaction,
          nextTransactionDelay
        },
        doAddTransactionEpic ? { addTransactionEpic: doAddTransactionEpic } : {}
      );
  }
};

const wrapEther = createAction(
  WRAP_ETHER,
  ({
    amountInWei,
    gasPrice = DEFAULT_GAS_PRICE
  }) =>
    getTokenContractInstance(TOKEN_WRAPPED_ETH).deposit({
      value: amountInWei,
      gasPrice
    })
);

const wrapEther$ = createPromiseActions("WRAP_UNWRAP/WRAP_ETHER");
const wrapETHTokenEpic = (
  withCallbacks,
  { doWrapEther = wrapEther, doAddTransactionEpic = null } = {}
) => (dispatch, getState) => {
  dispatch(wrapEther$.pending());
  const wrapAmount = getWrapAmount(getState(), WRAP_ETHER);
  return handleTransaction(
    {
      dispatch,
      transactionDispatcher: () =>
        dispatch(doWrapEther({ amountInWei: wrapAmount })),
      transactionType: TX_WRAP_ETHER,
      withCallbacks
    },
    doAddTransactionEpic ? { addTransactionEpic: doAddTransactionEpic } : {}
  );
};

const unwrapEther = createAction(
  UNWRAP_ETHER,
  async (
    amountInWei,
    { gasPrice = DEFAULT_GAS_PRICE } = {}
  ) =>
    getTokenContractInstance(TOKEN_WRAPPED_ETH).withdraw(amountInWei, {
      gasPrice
    })
);

const unwrapEther$ = createPromiseActions("WRAP_UNWRAP/UNWRAP_ETHER");
const unwrapEtherEpic = (
  withCallbacks,
  { doUnwrapEther = unwrapEther, doAddTransactionEpic = null } = {}
) => (dispatch, getState) => {
  dispatch(unwrapEther$.pending());
  return handleTransaction(
    {
      dispatch,
      transactionDispatcher: () =>
        dispatch(doUnwrapEther(getUnwrapAmount(getState(), UNWRAP_ETHER))),
      transactionType: TX_UNWRAP_ETHER,
      withCallbacks
    },
    doAddTransactionEpic ? { addTransactionEpic: doAddTransactionEpic } : {}
  );
};

const wrapGNTTokenAction = createAction(
  WRAP_TOKEN_WRAPPER,
  async ({
    brokerAddress,
    amountInWei,
    gasPrice = DEFAULT_GAS_PRICE
  }) =>
    getTokenContractInstance(TOKEN_GOLEM).transfer(brokerAddress, amountInWei, {
      gasPrice
    })
);

const wrapGNTToken = (
  { brokerAddress, amountInWei },
  withCallbacks,
  nextTransaction,
  {
    doWrapGNTTokenAction = wrapGNTTokenAction,
    doAddTransactionEpic = null,
    nextTransactionDelay = 3000
  } = {}
) => dispatch => {
  return handleTransaction(
    {
      dispatch,
      callsNext: true,
      transactionDispatcher: () =>
        dispatch(doWrapGNTTokenAction({ brokerAddress, amountInWei })),
      transactionType: TX_WRAP_TOKEN_WRAPPER,
      withCallbacks,
      txMeta: {
        txSubType: "wrapGNT"
      },
      onCallNextTransaction: nextTransaction,
      nextTransactionDelay
    },
    doAddTransactionEpic ? { addTransactionEpic: doAddTransactionEpic } : {}
  );
};

const wrapGNTToken$ = createPromiseActions("WRAP_UNWRAP/WRAP_GNT_TOKEN");
const wrapGNTTokenEpic = (
  withCallbacks,
  {
    doWrapGNTTokenAction = null,
    doAddTransactionEpic = null,
    doCreateGNTDepositBroker = null,
    nextTransactionDelay = null,
    doLoadDepositBrokerContractEpic = loadDepositBrokerContractEpic,
    doClearDepositBrokerEpic = clearDepositBrokerEpic,
    doLoadGNTBrokerAddressEpic = loadGNTBrokerAddressEpic,
    nestedDispatch = (...args) => args[0](...args.slice(1))
  } = {}
) => async (dispatch, getState) => {
  dispatch(wrapGNTToken$.pending());
  const depositBrokerAddress = wrapUnwrap.getBrokerAddress(
    getState(),
    TOKEN_GOLEM
  );
  const wrapAmount = getWrapAmount(getState(), WRAP_TOKEN_WRAPPER);
  if (!wrapUnwrap.isTokenBrokerInitiallyLoaded(getState(), TOKEN_GOLEM)) {
    await dispatch(loadGNTBrokerAddressEpic());
  }

  if (wrapUnwrap.hasTokenBroker(getState(), TOKEN_GOLEM)) {
    return dispatch(
      wrapGNTToken(
        { brokerAddress: depositBrokerAddress, amountInWei: wrapAmount },
        withCallbacks,
        async () => {
          await dispatch(doLoadDepositBrokerContractEpic(TOKEN_GOLEM));
          dispatch(doClearDepositBrokerEpic(TOKEN_GOLEM, withCallbacks));
        },
        Object.assign(
          {},
          doAddTransactionEpic ? { doAddTransactionEpic } : {},
          doWrapGNTTokenAction ? { doWrapGNTTokenAction } : {},
          nextTransactionDelay != null ? { nextTransactionDelay } : {}
        )
      )
    );
  } else {
    return dispatch(
      createDepositBrokerEpic(
        TOKEN_GOLEM,
        withCallbacks,
        async () => {
          await dispatch(doLoadGNTBrokerAddressEpic());
          await dispatch(doLoadDepositBrokerContractEpic(TOKEN_GOLEM));
          testActions.lastNestedWrapGNT = nestedDispatch(
            dispatch,
            wrapGNTToken(
              { brokerAddress: depositBrokerAddress, amountInWei: wrapAmount },
              withCallbacks,
              async () => {
                await dispatch(
                  doClearDepositBrokerEpic(TOKEN_GOLEM, withCallbacks)
                );
                dispatch(wrapGNTToken$.fulfilled());
                dispatch(resetActiveWrapForm(TX_WRAP_TOKEN_WRAPPER));
              },
              Object.assign(
                {},
                doAddTransactionEpic ? { doAddTransactionEpic } : {},
                doWrapGNTTokenAction ? { doWrapGNTTokenAction } : {},
                nextTransactionDelay != null ? { nextTransactionDelay } : {}
              )
            )
          );
        },
        Object.assign(
          {},
          doAddTransactionEpic ? { doAddTransactionEpic } : {},
          doCreateGNTDepositBroker ? { doCreateGNTDepositBroker } : {},
          nextTransactionDelay != null ? { nextTransactionDelay } : {}
        )
      )
    );
  }
};

const addressHasNoBrokerForToken = createAction(
  "WRAP_UNWRAP/ADDRESS_HAS_NO_BROKER_FOR_TOKEN",
  tokenName => tokenName
);

const loadDepositBrokerContractEpic = (tokenName = TOKEN_GOLEM) => (
  dispatch,
  getState
) => {
  const depositBrokerAddress = wrapUnwrap.getBrokerAddress(
    getState(),
    TOKEN_GOLEM
  );
  if (web3.toBigNumber(depositBrokerAddress).eq(0)) {
    dispatch(addressHasNoBrokerForToken(tokenName));
  } else {
    initDepositBrokerContract(tokenName, depositBrokerAddress);
  }
};

const clearDepositBroker = createAction(
  "WRAP_UNWRAP/CLEAR_DEPOSIT_BROKER",
  (
    tokenName,
    { gasPrice = DEFAULT_GAS_PRICE } = {}
  ) =>
    getDepositBrokerContractInstance(tokenName).clear({
      gasPrice
    })
);

const clearDepositBrokerEpic = (tokenName, withCallbacks) => dispatch => {
  return handleTransaction({
    dispatch,
    transactionDispatcher: () => dispatch(clearDepositBroker(tokenName)),
    transactionType: TX_WRAP_TOKEN_WRAPPER,
    txMeta: {
      txSubType: WRAP_UNWRAP_CLEAR_DEPOSIT_BROKER,
      tokenName
    },
    withCallbacks
  });
};

const unwrapGNTToken = createAction(
  UNWRAP_TOKEN_WRAPPER,
  async ({
    gasPrice = DEFAULT_GAS_PRICE,
    amountInWei
  }) =>
    getTokenContractInstance(TOKEN_WRAPPED_GNT).withdraw(amountInWei, {
      gasPrice
    })
);

const unwrapGNTToken$ = createPromiseActions("WRAP_UNWRAP/UNWRAP_GNT_TOKEN");
const unwrapGNTTokenEpic = (
  withCallbacks,
  { doUnwrapGNTToken = unwrapGNTToken, doAddTransactionEpic = null } = {}
) => (dispatch, getState) => {
  dispatch(unwrapGNTToken$.pending());
  return handleTransaction(
    {
      dispatch,
      transactionDispatcher: () =>
        dispatch(
          doUnwrapGNTToken({
            amountInWei: getUnwrapAmount(getState(), UNWRAP_TOKEN_WRAPPER)
          })
        ),
      transactionType: TX_UNWRAP_TOKEN_WRAPPER,
      withCallbacks
    },
    doAddTransactionEpic ? { addTransactionEpic: doAddTransactionEpic } : {}
  );
};

const wrapTokenEpic = withCallbacks => (dispatch, getState) => {
  switch (wrapUnwrap.activeUnwrappedToken(getState())) {
    case TOKEN_ETHER:
      {
        dispatch(wrapETHTokenEpic(withCallbacks));
      }
      break;
    case TOKEN_GOLEM:
      {
        dispatch(wrapGNTTokenEpic(withCallbacks));
      }
      break;
  }
};

const unwrapTokenEpic = withCallbacks => (dispatch, getState) => {
  switch (wrapUnwrap.activeUnwrappedToken(getState())) {
    case TOKEN_ETHER:
      {
        dispatch(unwrapEtherEpic(withCallbacks));
      }
      break;
    case TOKEN_GOLEM:
      {
        dispatch(unwrapGNTTokenEpic(withCallbacks));
      }
      break;
  }
};

const setWrapMax = () => (dispatch, getState) => {
  const activeUnwrappedToken = wrapUnwrap.activeUnwrappedToken(getState());
  const maxWrapValueInEther =
    activeUnwrappedToken === TOKEN_ETHER
      ? web3.fromWei(balances.ethBalance(getState()))
      : balances.tokenBalance(getState(), { tokenName: activeUnwrappedToken });
  if (maxWrapValueInEther) {
    dispatch(
      change(
        activeUnwrappedToken === TOKEN_ETHER ? "wrapEther" : "wrapTokenWrapper",
        "amount",
        maxWrapValueInEther.toString()
      )
    );
  }
};

const setUnwrapMax = () => (dispatch, getState) => {
  const activeWrappedToken = wrapUnwrap.activeWrappedToken(getState());
  const maxUnwrapValueInEther = balances.tokenBalance(getState(), {
    tokenName: activeWrappedToken
  });

  if (maxUnwrapValueInEther) {
    dispatch(
      change(
        activeWrappedToken === TOKEN_WRAPPED_ETH
          ? "unwrapEther"
          : "unwrapTokenWrapper",
        "amount",
        maxUnwrapValueInEther.toString()
      )
    );
  }
};

const resetActiveWrapForm = wrapType =>
  reset(wrapType === WRAP_ETHER ? "wrapEther" : "wrapTokenWrapper");

const setActiveUnwrapStatus = createAction(
  "WRAP_UNWRAP/SET_UNWRAP_STATUS",
  status => status
);
const resetActiveUnwrapStatus = createAction("WRAP_UNWRAP/SET_UNWRAP_STATUS");

const setActiveWrapStatus = createAction(
  "WRAP_UNWRAP/SET_WRAP_STATUS",
  status => status
);
const resetActiveWrapStatus = createAction("WRAP_UNWRAP/SET_WRAP_STATUS");

const resetActiveUnwrapForm = unwrapType =>
  reset(unwrapType === UNWRAP_ETHER ? "unwrapEther" : "unwrapTokenWrapper");

const actions = {
  loadGNTBrokerAddressEpic,
  wrapTokenEpic,
  unwrapTokenEpic,
  setActiveWrapUnwrappedToken,
  setWrapMax,
  setUnwrapMax,
  resetActiveWrapForm,
  resetActiveUnwrapForm
};

const testActions = {
  wrapETHTokenEpic,
  unwrapEtherEpic,
  wrapGNTTokenEpic,
  unwrapGNTTokenEpic,
  lastNestedWrapGNT: null
};

const reducer = handleActions(
  {
    [fulfilled(loadGNTBrokerAddress)]: (state, action) =>
      state.setIn(
        ["brokerAddresses", action.meta, TOKEN_GOLEM],
        action.payload
      ),
    [setActiveWrapUnwrappedToken]: (state, { payload }) => {
      return state.set("activeUnwrappedToken", payload);
    },
    [setActiveWrapStatus]: (state, payload) =>
      state.set("activeWrapStatus", payload),
    [resetActiveWrapStatus]: state => state.set("activeWrapStatus", null),
    [setActiveUnwrapStatus]: (state, payload) =>
      state.set("activeUnwrapStatus", payload),
    [resetActiveUnwrapStatus]: state => state.set("activeUnwrapStatus", null)
  },
  initialState
);

export default {
  actions,
  testActions,
  reducer
};