Skip to content
This repository has been archived by the owner on Dec 13, 2019. It is now read-only.

[Wallet-UI] ERC20 Token Support #2102

Merged
merged 22 commits into from
Aug 12, 2019
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions packages/wallet-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,20 @@ import {
import { EthereumService } from "./providers/EthereumService";
import { ActionType, ApplicationState } from "./store/types";
import { getUser } from "./store/user/user";
import { connectToWallet, getNodeTokens } from "./store/wallet/wallet";
import { connectToWallet } from "./store/wallet/wallet";
import { RoutePath } from "./types";

type AppProps = {
getUser: (provider: Web3Provider, history: History) => void;
connectToWallet: (provider: Web3Provider) => void;
getNodeTokens: (provider: Web3Provider) => void;
};

const App: React.FC<AppProps> = ({
getUser,
connectToWallet,
getNodeTokens
}) => {
const App: React.FC<AppProps> = ({ getUser, connectToWallet }) => {
const { provider } = useContext(EthereumService);
const history = createBrowserHistory();
useEffect(() => {
connectToWallet(provider);
getUser(provider, history);
getNodeTokens(provider);
});
return (
<Router history={history}>
Expand Down Expand Up @@ -71,7 +65,6 @@ export default connect(
getUser: (provider: Web3Provider, history: History) =>
dispatch(getUser(provider, history)),
connectToWallet: (provider: Web3Provider) =>
dispatch(connectToWallet(provider)),
getNodeTokens: (provider: Web3Provider) => dispatch(getNodeTokens(provider))
dispatch(connectToWallet(provider))
})
)(App);
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class FormInput extends React.Component<
onChange={event => this.handleChange(event)}
/>
{Array.isArray(units) && units.length === 1 ? (
<div className="unit">{units[0].name}</div>
<div className="unit">{units[0].shortName}</div>
) : Array.isArray(units) && units.length > 1 ? (
<select
className="unit-selector"
Expand Down
21 changes: 15 additions & 6 deletions packages/wallet-ui/src/pages/account-deposit/AccountDeposit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type AccountDepositState = {
ctaButtonText: string;
headerDetails: string;
};
tokenAddress?: string;
loading: boolean;
amount: BigNumberish;
};
Expand Down Expand Up @@ -74,14 +75,16 @@ export class AccountDeposit extends React.Component<
}
this.state = {
depositCaseVariables,
tokenAddress: undefined,
amount: parseEther(String(props.initialAmount || 0.1)),
loading: false
};
}

handleChange = ({ value }: InputChangeProps) => {
handleChange = ({ value, tokenAddress }: InputChangeProps) => {
this.setState({
...this.state,
tokenAddress,
amount: parseEther(value as string)
});
};
Expand All @@ -94,9 +97,11 @@ export class AccountDeposit extends React.Component<

createDepositData(
{ multisigAddress, nodeAddress, ethAddress }: User,
amount: BigNumberish
amount: BigNumberish,
tokenAddress?: string
): Deposit {
return {
tokenAddress,
nodeAddress,
ethAddress,
amount,
Expand All @@ -113,8 +118,8 @@ export class AccountDeposit extends React.Component<
render() {
const { walletState, deposit, history, user } = this.props;
const { provider } = this.context;
const { ethereumBalance, error, status, nodeAddresses } = walletState;
const { amount, loading, depositCaseVariables } = this.state;
const { ethereumBalance, error, status, tokenAddresses } = walletState;
const { amount, loading, depositCaseVariables, tokenAddress } = this.state;
const {
halfWidget,
header,
Expand All @@ -129,7 +134,7 @@ export class AccountDeposit extends React.Component<
label={<BalanceLabel available={formatEther(ethereumBalance)} />}
className="input--balance"
type="number"
units={nodeAddresses}
units={tokenAddresses}
name="amount"
min={0.02}
max={Number(ethereumBalance)}
Expand All @@ -149,7 +154,11 @@ export class AccountDeposit extends React.Component<
disabled={loading}
onClick={() => {
this.setState({ loading: true });
deposit(this.createDepositData(user, amount), provider, history);
deposit(
this.createDepositData(user, amount, tokenAddress),
provider,
history
);
}}
>
{!loading ? ctaButtonText : this.buttonText[status]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("<AccountWithdraw />", () => {
});

it("should render a Proceed button or Withdraw", () => {
const CTA = component.find(testSelector("deposit-button"));
const CTA = component.find(testSelector("withdraw-button"));
expect(CTA.exists()).toBe(true);
expect(["Proceed", "Withdraw"]).toContain(CTA.text());
});
Expand All @@ -89,7 +89,7 @@ describe("<AccountWithdraw />", () => {
component.find(testSelector("amount-input")).simulate("change", {
target: { value: "0.01", validity: { valid: true } }
});
component.find(testSelector("deposit-button")).simulate("click");
component.find(testSelector("withdraw-button")).simulate("click");
expect(props.history.location.pathname).toBe(RoutePath.Channels);
});
});
92 changes: 77 additions & 15 deletions packages/wallet-ui/src/pages/account-withdraw/AccountWithdraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ import {
Deposit,
ErrorData,
User,
WalletState
WalletState,
AssetType
} from "../../store/types";
import { WalletWithdrawTransition, withdraw } from "../../store/wallet/wallet";
import { RoutePath } from "../../types";
import { RoutePath, defaultToken } from "../../types";
import "./AccountWithdraw.scss";
import { Zero } from "ethers/constants";

const BalanceLabel: React.FC<{ available: string }> = ({ available }) => (
const BalanceLabel: React.FC<{ available: string; shortName: string }> = ({
available,
shortName
}) => (
<div className="balance-label">
<div>Available Balance</div>
<div>{available} ETH</div>
<div>
{available} {shortName}
</div>
</div>
);

Expand All @@ -43,6 +50,8 @@ type AccountWithdrawState = {
ctaButtonText: string;
headerDetails: string;
};
selectedToken: AssetType;
withdrawableTokens: AssetType[];
loading: boolean;
amount: BigNumberish;
};
Expand Down Expand Up @@ -72,14 +81,25 @@ export class AccountWithdraw extends React.Component<
}
this.state = {
withdrawCaseVariables,
withdrawableTokens: (
this.props.walletState.tokenAddresses || [defaultToken]
).filter(token => token.counterfactualBalance),
selectedToken:
this.props.walletState.tokenAddresses.find(
({ tokenAddress: ta }) => ta === defaultToken.tokenAddress
) || defaultToken,
amount: parseEther(String(props.initialAmount || 0.1)),
loading: false
};
}

handleChange = ({ value }: InputChangeProps) => {
handleChange = ({ value, tokenAddress }: InputChangeProps) => {
this.setState({
...this.state,
selectedToken:
this.state.withdrawableTokens.find(
({ tokenAddress: ta }) => ta === tokenAddress
) || this.state.withdrawableTokens[0],
amount: parseEther(value as string)
});
};
Expand All @@ -89,11 +109,13 @@ export class AccountWithdraw extends React.Component<
[WalletWithdrawTransition.WaitForFunds]: "Transfering funds"
};

createDepositData(
createTransactionData(
{ multisigAddress, nodeAddress, ethAddress }: User,
amount: BigNumberish
amount: BigNumberish,
tokenAddress?: string
): Deposit {
return {
tokenAddress,
nodeAddress,
ethAddress,
amount,
Expand All @@ -105,31 +127,63 @@ export class AccountWithdraw extends React.Component<
if (this.props.error !== prevProps.error) {
this.setState({ ...this.state, loading: false });
}
if (
this.props.walletState.tokenAddresses[0] !==
prevProps.walletState.tokenAddresses[0]
) {
this.setState({
...this.state,
withdrawableTokens: this.props.walletState.tokenAddresses.filter(
token => token.counterfactualBalance
),
selectedToken:
this.props.walletState.tokenAddresses.find(
({ tokenAddress: ta }) =>
ta === this.state.selectedToken.tokenAddress
) || defaultToken
});
}
}

render() {
const { walletState, withdraw, history, user } = this.props;
const { provider } = this.context;
const { ethereumBalance, error, status, nodeAddresses } = walletState;
const { amount, loading, withdrawCaseVariables } = this.state;
const { error, status } = walletState;
const {
amount,
loading,
withdrawCaseVariables,
withdrawableTokens,
selectedToken
} = this.state;
const {
halfWidget,
header,
headerDetails,
ctaButtonText
} = withdrawCaseVariables;

return (
<WidgetScreen header={header} half={halfWidget} exitable={false}>
<form>
<div className="details">{headerDetails}</div>
<FormInput
label={<BalanceLabel available={formatEther(ethereumBalance)} />}
label={
<BalanceLabel
available={formatEther(
(selectedToken && selectedToken.counterfactualBalance) || Zero
)}
shortName={selectedToken && selectedToken.shortName}
/>
}
className="input--balance"
type="number"
units={nodeAddresses}
units={withdrawableTokens}
name="amount"
min={0.02}
max={Number(ethereumBalance)}
max={Number(
(selectedToken && selectedToken.counterfactualBalance) || Zero
)}
value={formatEther(amount)}
step={0.01}
change={this.handleChange}
Expand All @@ -139,14 +193,22 @@ export class AccountWithdraw extends React.Component<
<div className="error">Ups! something broke</div>
) : null}
<FormButton
name="deposit"
name="withdraw"
type="button"
className="button"
spinner={loading}
disabled={loading}
disabled={loading || !!(error.message || error.code)}
onClick={() => {
this.setState({ loading: true });
withdraw(this.createDepositData(user, amount), provider, history);
withdraw(
this.createTransactionData(
user,
amount,
selectedToken && selectedToken.tokenAddress
),
provider,
history
);
}}
>
{!loading ? ctaButtonText : this.buttonText[status]}
Expand Down
23 changes: 21 additions & 2 deletions packages/wallet-ui/src/store/test-utils/ethereum.mock.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Zero } from "ethers/constants";
import { Zero, AddressZero } from "ethers/constants";
import { parseEther } from "ethers/utils";
import { JsonRPCResponse } from "web3/providers";
import {
CounterfactualEvent,
CounterfactualMethod,
EthereumGlobal
} from "../../types";
import { USER_MOCK_DATA } from "../user/user.mock";
import { USER_MOCK_DATA, USER_MOCK_BALANCE } from "../user/user.mock";

export const ETHEREUM_MOCK_TOKEN_ADDRESS = AddressZero;

export const ETHEREUM_MOCK_ADDRESS =
"0x9aF5D0dcABc31B1d80639ac3042b2aD754f072FE";
Expand All @@ -31,6 +33,8 @@ export const TRANSACTION_MOCK_HASH =

export const ETHEREUM_MOCK_BALANCE = parseEther("2.0");

export const MOCK_NETWORK = { name: "kovan" };

export const FREE_BALANCE_MOCK_AMOUNT = parseEther("1.0");

export const COUNTERFACTUAL_FREE_BALANCE_MOCK_AMOUNT = parseEther("1.0");
Expand Down Expand Up @@ -167,6 +171,21 @@ export default class EthereumMock implements EthereumGlobal {
};
}

if (eventOrMethod === CounterfactualMethod.RequestIndexedBalances) {
return {
jsonrpc: "2.0",
result: {
[ETHEREUM_MOCK_TOKEN_ADDRESS]: {
[this.mockBehaviors.nodeAddressFromUserMock
? FREE_BALANCE_MOCK_ADDRESS_FROM_USER_MOCK
: FREE_BALANCE_MOCK_ADDRESS]: USER_MOCK_BALANCE,
[COUNTERPARTY_FREE_BALANCE_MOCK_ADDRESS]: ETHEREUM_MOCK_BALANCE
}
},
id: Date.now()
};
}

if (
eventOrMethod === CounterfactualMethod.RequestChannels &&
!this.mockBehaviors.forceFailOnGetAllChannels
Expand Down
Loading