import { ZeroEx } from '0x.js';
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import {
Action,
ActionTypes,
BlockchainErrs,
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;
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: BlockchainErrs.NoError,
blockchainIsLoaded: false,
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.ResetState:
return INITIAL_STATE;
case ActionTypes.UpdateOrderSalt: {
return {
...state,
orderSalt: action.data,
};
}
case ActionTypes.UpdateNodeVersion: {
return {
...state,
nodeVersion: action.data,
};
}
case ActionTypes.UpdateOrderFillAmount: {
return {
...state,
orderFillAmount: action.data,
};
}
case ActionTypes.UpdateShouldBlockchainErrDialogBeOpen: {
return {
...state,
shouldBlockchainErrDialogBeOpen: action.data,
};
}
case ActionTypes.UpdateUserEtherBalance: {
return {
...state,
userEtherBalance: action.data,
};
}
case ActionTypes.UpdateUserSuppliedOrderCache: {
return {
...state,
userSuppliedOrderCache: action.data,
};
}
case ActionTypes.ClearTokenByAddress: {
return {
...state,
tokenByAddress: {},
};
}
case ActionTypes.AddTokenToTokenByAddress: {
const newTokenByAddress = state.tokenByAddress;
newTokenByAddress[action.data.address] = action.data;
return {
...state,
tokenByAddress: newTokenByAddress,
};
}
case ActionTypes.RemoveTokenFromTokenByAddress: {
const newTokenByAddress = state.tokenByAddress;
delete newTokenByAddress[action.data.address];
return {
...state,
tokenByAddress: newTokenByAddress,
};
}
case ActionTypes.UpdateTokenByAddress: {
const tokenByAddress = state.tokenByAddress;
const tokens = action.data;
_.each(tokens, token => {
const updatedToken = {
...tokenByAddress[token.address],
...token,
};
tokenByAddress[token.address] = updatedToken;
});
return {
...state,
tokenByAddress,
};
}
case ActionTypes.UpdateTokenStateByAddress: {
const tokenStateByAddress = state.tokenStateByAddress;
const updatedTokenStateByAddress = action.data;
_.each(updatedTokenStateByAddress, (tokenState: TokenState, address: string) => {
const updatedTokenState = {
...tokenStateByAddress[address],
...tokenState,
};
tokenStateByAddress[address] = updatedTokenState;
});
return {
...state,
tokenStateByAddress,
};
}
case ActionTypes.RemoveFromTokenStateByAddress: {
const tokenStateByAddress = state.tokenStateByAddress;
const tokenAddress = action.data;
delete tokenStateByAddress[tokenAddress];
return {
...state,
tokenStateByAddress,
};
}
case ActionTypes.ReplaceTokenAllowanceByAddress: {
const tokenStateByAddress = state.tokenStateByAddress;
const allowance = action.data.allowance;
const tokenAddress = action.data.address;
tokenStateByAddress[tokenAddress] = {
...tokenStateByAddress[tokenAddress],
allowance,
};
return {
...state,
tokenStateByAddress,
};
}
case ActionTypes.ReplaceTokenBalanceByAddress: {
const tokenStateByAddress = state.tokenStateByAddress;
const balance = action.data.balance;
const tokenAddress = action.data.address;
tokenStateByAddress[tokenAddress] = {
...tokenStateByAddress[tokenAddress],
balance,
};
return {
...state,
tokenStateByAddress,
};
}
case ActionTypes.UpdateTokenBalanceByAddress: {
const tokenStateByAddress = state.tokenStateByAddress;
const balanceDelta = action.data.balanceDelta;
const tokenAddress = action.data.address;
const currBalance = tokenStateByAddress[tokenAddress].balance;
tokenStateByAddress[tokenAddress] = {
...tokenStateByAddress[tokenAddress],
balance: currBalance.plus(balanceDelta),
};
return {
...state,
tokenStateByAddress,
};
}
case ActionTypes.UpdateOrderSignatureData: {
return {
...state,
orderSignatureData: action.data,
};
}
case ActionTypes.UpdateScreenWidth: {
return {
...state,
screenWidth: action.data,
};
}
case ActionTypes.UpdateBlockchainIsLoaded: {
return {
...state,
blockchainIsLoaded: action.data,
};
}
case ActionTypes.BlockchainErrEncountered: {
return {
...state,
blockchainErr: action.data,
};
}
case ActionTypes.UpdateNetworkId: {
return {
...state,
networkId: action.data,
};
}
case ActionTypes.UpdateChosenAssetToken: {
const newSideToAssetToken = {
...state.sideToAssetToken,
[action.data.side]: action.data.token,
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.UpdateChosenAssetTokenAddress: {
const newAssetToken = state.sideToAssetToken[action.data.side];
newAssetToken.address = action.data.address;
const newSideToAssetToken = {
...state.sideToAssetToken,
[action.data.side]: newAssetToken,
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.SwapAssetTokens: {
const newSideToAssetToken = {
[Side.Deposit]: state.sideToAssetToken[Side.Receive],
[Side.Receive]: state.sideToAssetToken[Side.Deposit],
};
return {
...state,
sideToAssetToken: newSideToAssetToken,
};
}
case ActionTypes.UpdateOrderExpiry: {
return {
...state,
orderExpiryTimestamp: action.data,
};
}
case ActionTypes.UpdateOrderTakerAddress: {
return {
...state,
orderTakerAddress: action.data,
};
}
case ActionTypes.UpdateUserAddress: {
return {
...state,
userAddress: action.data,
};
}
// Docs
case ActionTypes.UpdateLibraryVersion: {
return {
...state,
docsVersion: action.data,
};
}
case ActionTypes.UpdateAvailableLibraryVersions: {
return {
...state,
availableDocVersions: action.data,
};
}
// Shared
case ActionTypes.ShowFlashMessage: {
return {
...state,
flashMessage: action.data,
};
}
case ActionTypes.HideFlashMessage: {
return {
...state,
flashMessage: undefined,
};
}
case ActionTypes.UpdateProviderType: {
return {
...state,
providerType: action.data,
};
}
case ActionTypes.UpdateInjectedProviderName: {
return {
...state,
injectedProviderName: action.data,
};
}
default:
return state;
}
}