Skip to content

Commit

Permalink
Merge pull request LedgerHQ#159 from LedgerHQ/erc20
Browse files Browse the repository at this point in the history
Add erc20 full example
  • Loading branch information
gre committed Mar 1, 2019
2 parents 03ad693 + 10ff741 commit 8b637f0
Show file tree
Hide file tree
Showing 21 changed files with 1,176 additions and 184 deletions.
13 changes: 7 additions & 6 deletions demo/package.json
Expand Up @@ -3,24 +3,25 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@ledgerhq/hw-transport": "^4.32.0",
"@ledgerhq/hw-transport-u2f": "^4.39.0",
"@ledgerhq/hw-transport-web-ble": "^4.39.0",
"@ledgerhq/hw-transport-webusb": "^4.39.0",
"@ledgerhq/hw-transport": "^4.41.1",
"@ledgerhq/hw-transport-u2f": "^4.41.1",
"@ledgerhq/hw-transport-web-ble": "^4.41.1",
"@ledgerhq/hw-transport-webusb": "^4.41.1",
"@ledgerhq/live-common": "file:..",
"axios": "^0.18.0",
"babel-polyfill": "^6.26.0",
"bignumber.js": "^7.2.1",
"flow-bin": "^0.73.0",
"material-ui": "^1.0.0-beta.44",
"qrcode": "^1.3.0",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-inspector": "^2.3.0",
"react-redux": "^5.0.7",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"react-scripts": "2.0.5",
"react-select": "^2.4.1",
"react-table": "^6.8.6",
"redux": "^4.0.1",
"redux-devtools-extension": "^2.13.2",
Expand Down
22 changes: 16 additions & 6 deletions demo/public/index.html
@@ -1,16 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
/>

<!--
Notice the use of %PUBLIC_URL% in the tags above.
Expand Down Expand Up @@ -40,5 +46,9 @@
-->

<script type="text/javascript" src="%PUBLIC_URL%/gif.js"></script>
<script>
window.LEDGER_CV_API =
window.LEDGER_CV_API || "https://countervalues.api.live.ledger.com";
</script>
</body>
</html>
34 changes: 34 additions & 0 deletions demo/src/demos/erc20/AccountField.js
@@ -0,0 +1,34 @@
// @flow
import React from "react";
import styled from "styled-components";

const Input = styled.input`
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
color: #333;
width: 100%;
padding: 0.5em 1em;
box-sizing: border-box;
`;

type Props = {
value: string,
onChange: string => void,
autoFocus?: boolean
};

const AccountField = ({ value, onChange, autoFocus }: Props) => {
return (
<Input
type="text"
placeholder="ETH derivation path"
value={value}
onChange={onChange}
autoFocus={autoFocus}
/>
);
};

export default AccountField;
34 changes: 34 additions & 0 deletions demo/src/demos/erc20/AddressField.js
@@ -0,0 +1,34 @@
// @flow
import React from "react";
import styled from "styled-components";

const Input = styled.input`
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
color: #333;
width: 100%;
padding: 0.5em 1em;
box-sizing: border-box;
`;

type Props = {
value: string,
onChange: string => void,
autoFocus?: boolean
};

const AddressField = ({ value, onChange, autoFocus }: Props) => {
return (
<Input
type="text"
placeholder="Recipient address"
value={value}
onChange={e => onChange(e.target.value)}
autoFocus={autoFocus}
/>
);
};

export default AddressField;
78 changes: 78 additions & 0 deletions demo/src/demos/erc20/AmountField.js
@@ -0,0 +1,78 @@
// @flow
import React, { useState } from "react";
import { BigNumber } from "bignumber.js";
import styled from "styled-components";
import {
formatCurrencyUnit,
sanitizeValueString
} from "@ledgerhq/live-common/lib/currencies";
import type { Unit } from "@ledgerhq/live-common/lib/types";

const Container = styled.div`
position: relative;
`;

const Code = styled.div`
position: absolute;
top: 0.5em;
right: 1em;
font-size: 16px;
color: #ccc;
pointer-event: none;
`;

const Input = styled.input`
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
color: #333;
width: 100%;
padding: 0.5em 1em;
box-sizing: border-box;
`;

type Props = {
unit: Unit,
value: BigNumber,
onChange: BigNumber => void,
placeholder?: string,
autoFocus?: boolean
};

const AmountField = ({ unit, value, autoFocus, onChange }: Props) => {
const [isFocused, setFocused] = useState(autoFocus);
const formatted = formatCurrencyUnit(unit, value, {
useGrouping: !isFocused,
disableRounding: true
});
const initialText = value.isZero() ? "" : formatted;
const [text, setText] = useState(initialText);

return (
<Container>
<Input
type="text"
value={text}
placeholder={formatted}
autoFocus={autoFocus}
onFocus={() => setFocused(true)}
onBlur={() => {
setFocused(false);
setText(initialText);
}}
onChange={e => {
const r = sanitizeValueString(unit, e.target.value);
const satoshiValue = BigNumber(r.value);
if (!value || !value.isEqualTo(satoshiValue)) {
onChange(satoshiValue);
}
setText(r.display);
}}
/>
<Code>{unit.code}</Code>
</Container>
);
};

export default AmountField;
62 changes: 62 additions & 0 deletions demo/src/demos/erc20/GasLimitField.js
@@ -0,0 +1,62 @@
// @flow
import React, { useState } from "react";
import { BigNumber } from "bignumber.js";
import styled from "styled-components";

const Container = styled.div`
position: relative;
`;

const Desc = styled.div`
position: absolute;
top: 0.5em;
right: 1em;
font-size: 16px;
color: #ccc;
pointer-event: none;
`;

const Input = styled.input`
outline: none;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
color: #333;
width: 100%;
padding: 0.5em 1em;
box-sizing: border-box;
`;

type Props = {
value: BigNumber,
onChange: BigNumber => void,
placeholder?: string,
autoFocus?: boolean
};

const AmountField = ({ value, autoFocus, onChange }: Props) => {
const formatted = value.toString();
const initialText = value.isZero() ? "" : formatted;
const [text, setText] = useState(initialText);

return (
<Container>
<Input
type="text"
value={text}
placeholder={formatted}
autoFocus={autoFocus}
onChange={e => {
const r = BigNumber(e.target.value).integerValue();
if (!value || !value.isEqualTo(r)) {
onChange(r);
}
setText(value.toString());
}}
/>
<Desc>Gas limit</Desc>
</Container>
);
};

export default AmountField;
27 changes: 27 additions & 0 deletions demo/src/demos/erc20/SendButton.js
@@ -0,0 +1,27 @@
// @flow
import React from "react";
import styled from "styled-components";

const Button = styled.div`
padding: 0.6em 1.2em;
font-size: 16px;
color: ${props => (props.disabled ? "#999" : "#fff")};
background-color: ${props => (props.disabled ? "#eee" : "#6490f1")};
border-radius: 4px;
cursor: pointer;
text-align: center;
`;

type Props = {
onClick: () => void,
title: string,
disabled?: boolean
};

const SendButton = ({ onClick, title, disabled }: Props) => (
<Button onClick={disabled ? undefined : onClick} disabled={disabled}>
{title}
</Button>
);

export default SendButton;
52 changes: 52 additions & 0 deletions demo/src/demos/erc20/TokenSelect.js
@@ -0,0 +1,52 @@
// @flow
import React, { useState, useEffect } from "react";
import Select from "react-select";
import { listTokens } from "@ledgerhq/live-common/lib/currencies";
import type { CurrencyToken } from "@ledgerhq/live-common/lib/types";
import countervalues from "./countervalues";

let tickers;
const tickersP = countervalues.fetchTickersByMarketcap().then(t => {
tickers = t;
return t;
});

const rank = token => {
const i = tickers.indexOf(token.ticker);
if (i === -1) return Infinity;
return i;
};

const getSortedTokens = () => {
let tokens = listTokens();
if (!tickers) return tokens;
return tokens.slice(0).sort((a, b) => rank(a) - rank(b));
};

type Props = {
value: ?CurrencyToken,
onChange: (?CurrencyToken) => void
};

const TokenSelect = ({ value, onChange }: Props) => {
const [tokens, setTokens] = useState(getSortedTokens);

useEffect(() => {
if (!tickers) {
tickersP.then(() => setTokens(getSortedTokens()));
}
}, []);

return (
<Select
value={value}
options={tokens}
onChange={onChange}
placeholder="Select an ERC20"
getOptionLabel={token => `${token.name} (${token.ticker})`}
getOptionValue={token => token.ticker}
/>
);
};

export default TokenSelect;
13 changes: 13 additions & 0 deletions demo/src/demos/erc20/countervalues.js
@@ -0,0 +1,13 @@
// @flow

import axios from "axios";
import createCounterValues from "@ledgerhq/live-common/lib/countervalues";

export default createCounterValues({
network: axios,
log: (...args) => console.log(...args), // eslint-disable-line no-console
getAPIBaseURL: () => window.LEDGER_CV_API,
storeSelector: state => state.countervalues,
pairsSelector: () => [],
setExchangePairsAction: () => ({})
});

0 comments on commit 8b637f0

Please sign in to comment.