import {ZeroEx} from '0x.js';
import BigNumber from 'bignumber.js';
import * as _ from 'lodash';
import {
Action,
ActionTypes,
BlockchainErrs,
Direction,
GenerateOrderSteps,
Order,
ProviderType,
ScreenWidths,
Side,
SideToAssetToken,
SignatureData,
TokenByAddress,
TokenState,
TokenStateByAddress,
} from 'ts/types';
import {utils} from 'ts/utils/utils';
// Instead of defaulting the docs version to an empty string, we pre-populate it with
// a valid version value. This does not need to be updated however, since onLoad, it
// is always replaced with a value retrieved from our S3 bucket.
const DEFAULT_DOCS_VERSION = '0.0.0';
export interface State {
// Portal
blockchainErr: BlockchainErrs;
blockchainIsLoaded: boolean;
generateOrderStep: GenerateOrderSteps;
networkId: number;
orderExpiryTimestamp: BigNumber;
orderFillAmount: BigNumber;
orderTakerAddress: string;
orderSignatureData: SignatureData;
orderSalt: BigNumber;
nodeVersion: string;
screenWidth: ScreenWidths;
shouldBlockchainErrDialogBeOpen: boolean;
sideToAssetToken: SideToAssetToken;
tokenByAddress: TokenByAddress;
tokenStateByAddress: TokenStateByAddress;
userAddress: string;
userEtherBalance: BigNumber;
// Note: cache of supplied orderJSON in fill order step. Do not use for anything else.
userSuppliedOrderCache: Order;
// Docs
docsVersion: string;
availableDocVersions: string[];
// Shared
flashMessage: string|React.ReactNode;
providerType: ProviderType;
injectedProviderName: string;
}
const INITIAL_STATE: State = {
// Portal
blockchainErr: '',
blockchainIsLoaded: false,
generateOrderStep: GenerateOrderSteps.ChooseAssets,
networkId: undefined,
orderExpiryTimestamp: utils.initialOrderExpiryUnixTimestampSec(),
orderFillAmount: undefined,
orderSignatureData: {
hash: '',
r: '',
s: '',
v: 27,
},
orderTakerAddress: '',
orderSalt: ZeroEx.generatePseudoRandomSalt(),
nodeVersion: undefined,
screenWidth: utils.getScreenWidth(),
shouldBlockchainErrDialogBeOpen: false,
sideToAssetToken: {
[Side.deposit]: {},
[Side.receive]: {},
},
tokenByAddress: {},
tokenStateByAddress: {},
userAddress: '',
userEtherBalance: new BigNumber(0),
userSuppliedOrderCache: undefined,
// Docs
docsVersion: DEFAULT_DOCS_VERSION,
availableDocVersions: [DEFAULT_DOCS_VERSION],
// Shared
flashMessage: undefined,
providerType: ProviderType.INJECTED,
injectedProviderName: '',
};
export function reducer(state: State = INITIAL_STATE, action: Action) {
switch (action.type) {
// Portal
case ActionTypes.RESET_STATE:
return INITIAL_STATE;
case ActionTypes.UPDATE_ORDER_SALT: {
return _.assign({}, state, {
orderSalt: action.data,
});
}
case ActionTypes.UPDATE_NODE_VERSION: {
return _.assign({}, state, {
nodeVersion: action.data,
});
}
case ActionTypes.UPDATE_ORDER_FILL_AMOUNT: {
return _.assign({}, state, {
orderFillAmount: action.data,
});
}
case ActionTypes.UPDATE_SHOULD_BLOCKCHAIN_ERR_DIALOG_BE_OPEN: {
return _.assign({}, state, {
shouldBlockchainErrDialogBeOpen: action.data,
});
}
case ActionTypes.UPDATE_USER_ETHER_BALANCE: {
return _.assign({}, state, {
userEtherBalance: action.data,
});
}
case ActionTypes.UPDATE_USER_SUPPLIED_ORDER_CACHE: {
return _.assign({}, state, {
userSuppliedOrderCache: action.data,
});
}
case ActionTypes.CLEAR_TOKEN_BY_ADDRESS: {
return _.assign({}, state, {
tokenByAddress: {},
});
}
case ActionTypes.ADD_TOKEN_TO_TOKEN_BY_ADDRESS: {
const newTokenByAddress = state.tokenByAddress;
newTokenByAddress[action.data.address] = action.data;
return _.assign({}, state, {
tokenByAddress: newTokenByAddress,
});
}
case ActionTypes.REMOVE_TOKEN_TO_TOKEN_BY_ADDRESS: {
const newTokenByAddress = state.tokenByAddress;
delete newTokenByAddress[action.data.address];
return _.assign({}, state, {
tokenByAddress: newTokenByAddress,
});
}
case ActionTypes.UPDATE_TOKEN_BY_ADDRESS: {
const tokenByAddress = state.tokenByAddress;
const tokens = action.data;
_.each(tokens, token => {
const updatedToken = _.assign({}, tokenByAddress[token.address], token);
tokenByAddress[token.address] = updatedToken;
});
return _.assign({}, state, {
tokenByAddress,
});
}
case ActionTypes.UPDATE_TOKEN_STATE_BY_ADDRESS: {
const tokenStateByAddress = state.tokenStateByAddress;
const updatedTokenStateByAddress = action.data;
_.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => {
const updatedTokenState = _.assign({}, tokenStateByAddress[address], tokenState);
tokenStateByAddress[address] = updatedTokenState;
});
return _.assign({}, state, {
tokenStateByAddress,
});
}
case ActionTypes.REMOVE_FROM_TOKEN_STATE_BY_ADDRESS: {
const tokenStateByAddress = state.tokenStateByAddress;
const tokenAddress = action.data;
delete tokenStateByAddress[tokenAddress];
return _.assign({}, state, {
tokenStateByAddress,
});
}
case ActionTypes.REPLACE_TOKEN_ALLOWANCE_BY_ADDRESS: {
const tokenStateByAddress = state.tokenStateByAddress;
const allowance = action.data.allowance;
const tokenAddress = action.data.address;
tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
allowance,
});
return _.assign({}, state, {
tokenStateByAddress,
});
}
case ActionTypes.REPLACE_TOKEN_BALANCE_BY_ADDRESS: {
const tokenStateByAddress = state.tokenStateByAddress;
const balance = action.data.balance;
const tokenAddress = action.data.address;
tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
balance,
});
return _.assign({}, state, {
tokenStateByAddress,
});
}
case ActionTypes.UPDATE_TOKEN_BALANCE_BY_ADDRESS: {
const tokenStateByAddress = state.tokenStateByAddress;
const balanceDelta = action.data.balanceDelta;
const tokenAddress = action.data.address;
const currBalance = tokenStateByAddress[tokenAddress].balance;
tokenStateByAddress[tokenAddress] = _.assign({}, tokenStateByAddress[tokenAddress], {
balance: currBalance.plus(balanceDelta),
});
return _.assign({}, state, {
tokenStateByAddress,
});
}
case ActionTypes.UPDATE_ORDER_SIGNATURE_DATA: {
return _.assign({}, state, {
orderSignatureData: action.data,
});
}
case ActionTypes.UPDATE_SCREEN_WIDTH: {
return _.assign({}, state, {
screenWidth: action.data,
});
}
case ActionTypes.UPDATE_BLOCKCHAIN_IS_LOADED: {
return _.assign({}, state, {
blockchainIsLoaded: action.data,
});
}
case ActionTypes.BLOCKCHAIN_ERR_ENCOUNTERED: {
return _.assign({}, state, {
blockchainErr: action.data,
});
}
case ActionTypes.UPDATE_NETWORK_ID: {
return _.assign({}, state, {
networkId: action.data,
});
}
case ActionTypes.UPDATE_GENERATE_ORDER_STEP: {
const direction = action.data;
let nextGenerateOrderStep = state.generateOrderStep;
if (direction === Direction.forward) {
nextGenerateOrderStep += 1;
} else if (state.generateOrderStep !== 0) {
nextGenerateOrderStep -= 1;
}
return _.assign({}, state, {
generateOrderStep: nextGenerateOrderStep,
});
}
case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN: {
const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
[action.data.side]: action.data.token,
});
return _.assign({}, state, {
sideToAssetToken: newSideToAssetToken,
});
}
case ActionTypes.UPDATE_CHOSEN_ASSET_TOKEN_ADDRESS: {
const newAssetToken = state.sideToAssetToken[action.data.side];
newAssetToken.address = action.data.address;
const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
[action.data.side]: newAssetToken,
});
return _.assign({}, state, {
sideToAssetToken: newSideToAssetToken,
});
}
case ActionTypes.SWAP_ASSET_TOKENS: {
const newSideToAssetToken = _.assign({}, state.sideToAssetToken, {
[Side.deposit]: state.sideToAssetToken[Side.receive],
[Side.receive]: state.sideToAssetToken[Side.deposit],
});
return _.assign({}, state, {
sideToAssetToken: newSideToAssetToken,
});
}
case ActionTypes.UPDATE_ORDER_EXPIRY: {
return _.assign({}, state, {
orderExpiryTimestamp: action.data,
});
}
case ActionTypes.UPDATE_ORDER_TAKER_ADDRESS: {
return _.assign({}, state, {
orderTakerAddress: action.data,
});
}
case ActionTypes.UPDATE_USER_ADDRESS: {
return _.assign({}, state, {
userAddress: action.data,
});
}
// Docs
case ActionTypes.UPDATE_LIBRARY_VERSION: {
return _.assign({}, state, {
docsVersion: action.data,
});
}
case ActionTypes.UPDATE_AVAILABLE_LIBRARY_VERSIONS: {
return _.assign({}, state, {
availableDocVersions: action.data,
});
}
// Shared
case ActionTypes.SHOW_FLASH_MESSAGE: {
return _.assign({}, state, {
flashMessage: action.data,
});
}
case ActionTypes.HIDE_FLASH_MESSAGE: {
return _.assign({}, state, {
flashMessage: undefined,
});
}
case ActionTypes.UPDATE_PROVIDER_TYPE: {
return _.assign({}, state, {
providerType: action.data,
});
}
case ActionTypes.UPDATE_INJECTED_PROVIDER_NAME: {
return _.assign({}, state, {
injectedProviderName: action.data,
});
}
default:
return state;
}
}