Merge branch 'master' of github.com:status-im/keycard-redeem

This commit is contained in:
Michele Balistreri 2020-04-30 08:34:41 +03:00
commit 60133de7d1
No known key found for this signature in database
GPG Key ID: E9567DA33A4F791A
27 changed files with 6401 additions and 461 deletions

View File

View File

@ -1,10 +0,0 @@
<html>
<head>
<title>Keycard Redeem</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
</head>
<body>
<div id="root"></div>
<script src="/js/app.js"></script>
</body>
</html>

View File

View File

@ -36,6 +36,19 @@ module.exports = {
ERC20BucketFactory: { ERC20BucketFactory: {
params: [], params: [],
}, },
ERC20Bucket: {
params: [],
proxyFor: "Bucket",
deploy: false,
},
NFTBucketFactory: {
params: [],
},
NFTBucket: {
params: [],
proxyFor: "Bucket",
deploy: false,
},
} }
}, },

View File

@ -22,8 +22,8 @@ module.exports = {
// A new DApp can be created from that template with: // A new DApp can be created from that template with:
// embark new --template typescript // embark new --template typescript
// NOTE: the `--template` option is DEPRECATED in v5. // NOTE: the `--template` option is DEPRECATED in v5.
enabled: true enabled: false
// Setting `enabled: false` in this config will disable Embark's built-in Webpack // Setting `enabled: false` in this config will disable Embark's built-in Webpack
// pipeline. The developer will need to use a different frontend build tool, such as // pipeline. The developer will need to use a different frontend build tool, such as
// `create-react-app` or Angular CLI to build their dapp // `create-react-app` or Angular CLI to build their dapp
}; };

View File

@ -67,14 +67,21 @@ abstract contract Bucket {
function bucketType() virtual external returns (uint256); function bucketType() virtual external returns (uint256);
function redeem(Redeem calldata _redeem, bytes calldata _sig) external { function redeem(Redeem calldata _redeem, bytes calldata _sig) external {
validateRedeem(_redeem, maxTxDelayInBlocks, expirationTime, startTime); // validate Redeem
require(_redeem.blockNumber < block.number, "transaction cannot be in the future");
require(_redeem.blockNumber >= (block.number - maxTxDelayInBlocks), "transaction too old");
require(_redeem.blockHash == blockhash(_redeem.blockNumber), "invalid block hash");
require(block.timestamp < expirationTime, "expired redeemable");
require(block.timestamp > startTime, "reedeming not yet started");
address recipient = recoverSigner(DOMAIN_SEPARATOR, _redeem, _sig); address recipient = recoverSigner(DOMAIN_SEPARATOR, _redeem, _sig);
Redeemable storage redeemable = redeemables[recipient]; Redeemable storage redeemable = redeemables[recipient];
require(redeemable.recipient == recipient, "not found"); require(redeemable.recipient == recipient, "not found");
validateCode(_redeem, redeemable.code); // validate code
bytes32 codeHash = keccak256(abi.encodePacked(_redeem.code));
require(codeHash == redeemable.code, "invalid code");
uint256 data = redeemable.data; uint256 data = redeemable.data;
@ -109,15 +116,6 @@ abstract contract Bucket {
require(block.timestamp >= _expirationTime, "not expired yet"); require(block.timestamp >= _expirationTime, "not expired yet");
} }
function validateRedeem(Redeem memory _redeem, uint256 _maxTxDelayInBlocks, uint256 _expirationTime, uint256 _startTime) internal view {
require(_redeem.blockNumber < block.number, "transaction cannot be in the future");
require(_redeem.blockNumber >= (block.number - _maxTxDelayInBlocks), "transaction too old");
require(_redeem.blockHash == blockhash(_redeem.blockNumber), "invalid block hash");
require(block.timestamp < _expirationTime, "expired redeemable");
require(block.timestamp > _startTime, "reedeming not yet started");
}
function hashRedeem(Redeem memory _redeem) internal pure returns (bytes32) { function hashRedeem(Redeem memory _redeem) internal pure returns (bytes32) {
return keccak256(abi.encode( return keccak256(abi.encode(
REDEEM_TYPEHASH, REDEEM_TYPEHASH,
@ -155,9 +153,4 @@ abstract contract Bucket {
return ecrecover(digest, v, r, s); return ecrecover(digest, v, r, s);
} }
function validateCode(Redeem memory _redeem, bytes32 _code) internal pure {
bytes32 codeHash = keccak256(abi.encodePacked(_redeem.code));
require(codeHash == _code, "invalid code");
}
} }

View File

@ -1,11 +1,5 @@
{ {
"contracts": ["contracts/**"], "contracts": ["contracts/**"],
"app": {
"css/app.css": ["app/css/**"],
"js/app.js": ["app/js/index.tsx"],
"images/": ["app/images/**"],
"index.html": "app/index.html"
},
"buildDir": "dist/", "buildDir": "dist/",
"config": "config/", "config": "config/",
"versions": { "versions": {
@ -26,5 +20,5 @@
"optimize-runs": 200 "optimize-runs": 200
} }
}, },
"generationDir": "embarkArtifacts" "generationDir": "src/embarkArtifacts"
} }

View File

@ -3,28 +3,42 @@
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"scripts": { "scripts": {
"test": "embark test" "test": "embark test",
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/history": "^4.7.5",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.5",
"bn.js": "^5.1.1",
"connected-react-router": "^6.7.0", "connected-react-router": "^6.7.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"eth-sig-util": "^2.5.3", "eth-sig-util": "^2.5.3",
"history": "^4.10.1", "history": "^4.10.1",
"minimist": "^1.2.3", "minimist": "^1.2.3",
"react": "^16.12.0", "react": "^16.13.1",
"react-dom": "^16.12.0", "react-dom": "^16.13.1",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"react-router": "^5.1.2", "react-router": "^5.1.2",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"web3": "^1.2.6" "typescript": "^3.8.3",
"web3": "^1.2.6",
"web3-eth": "^1.2.6"
},
"eslintConfig": {
"extends": "react-app"
}, },
"devDependencies": { "devDependencies": {
"@babel/preset-typescript": "^7.8.3",
"embark": "^5.3.0-nightly.7", "embark": "^5.3.0-nightly.7",
"embark-geth": "^5.3.0-nightly.7", "embark-geth": "^5.3.0-nightly.7",
"embark-graph": "^5.3.0-nightly.7", "embark-graph": "^5.3.0-nightly.7",
@ -38,7 +52,18 @@
"embarkjs-ipfs": "^5.3.0-nightly.4", "embarkjs-ipfs": "^5.3.0-nightly.4",
"embarkjs-swarm": "^5.3.0-nightly.4", "embarkjs-swarm": "^5.3.0-nightly.4",
"embarkjs-web3": "^5.3.0-nightly.4", "embarkjs-web3": "^5.3.0-nightly.4",
"embarkjs-whisper": "^5.3.0-nightly.4", "embarkjs-whisper": "^5.3.0-nightly.4"
"typescript": "^3.8.3" },
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
} }
} }

13
public/index.html Normal file
View File

@ -0,0 +1,13 @@
<html>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<title>Keycard Redeem</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/css/app.css" type="text/css" media="screen" charset="utf-8">
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

View File

@ -195,4 +195,4 @@ async function run() {
process.exit(0); process.exit(0);
} }
run(); run();

View File

@ -1,8 +1,7 @@
import { RootState } from '../reducers'; import { RootState } from '../reducers';
import ERC20Bucket from '../../../embarkArtifacts/contracts/ERC20Bucket'; import ERC20Bucket from '../embarkArtifacts/contracts/ERC20Bucket';
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed'; import IERC20Detailed from '../embarkArtifacts/contracts/IERC20Detailed';
import { config } from "../config"; import { config } from "../config";
import { Contract } from 'web3-eth-contract';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
export const ERROR_REDEEMABLE_NOT_FOUND = "ERROR_REDEEMABLE_NOT_FOUND"; export const ERROR_REDEEMABLE_NOT_FOUND = "ERROR_REDEEMABLE_NOT_FOUND";
@ -129,9 +128,10 @@ export const loadRedeemable = (bucketAddress: string, recipientAddress: string)
return async (dispatch: Dispatch, getState: () => RootState) => { return async (dispatch: Dispatch, getState: () => RootState) => {
dispatch(loadingRedeemable(bucketAddress, recipientAddress)); dispatch(loadingRedeemable(bucketAddress, recipientAddress));
const bucket = newBucketContract(bucketAddress); const bucket = newBucketContract(bucketAddress);
bucket.methods.expirationTime().call().then(expirationTime => { bucket.methods.expirationTime().call().then((expirationTime: number) => {
bucket.methods.redeemables(recipientAddress).call().then((result: any) => { bucket.methods.redeemables(recipientAddress).call().then((result: any) => {
const { recipient, amount, code } = result; const { recipient, data, code } = result;
const amount = data;
if (amount === "0") { if (amount === "0") {
dispatch(redeemableNotFound()) dispatch(redeemableNotFound())
return; return;
@ -139,20 +139,21 @@ export const loadRedeemable = (bucketAddress: string, recipientAddress: string)
dispatch(redeemableLoaded(expirationTime, recipient, amount, code)); dispatch(redeemableLoaded(expirationTime, recipient, amount, code));
dispatch<any>(loadToken(bucket)) dispatch<any>(loadToken(bucket))
}).catch(err => { }).catch((err: string) => {
dispatch(errorLoadingRedeemable(err)) dispatch(errorLoadingRedeemable(err))
console.error("err: ", err) console.error("err: ", err)
}) })
}).catch(err => { }).catch((err: string) => {
dispatch(errorLoadingRedeemable(`error loading expirationTime: ${err}`)) dispatch(errorLoadingRedeemable(`error loading expirationTime: ${err}`))
console.error("err: ", err) console.error("err: ", err)
}); });
}; };
}; };
export const loadToken = (bucket: Contract) => { //FIXME: set the proper Contract type
export const loadToken = (bucket: any) => {
return (dispatch: Dispatch, getState: () => RootState) => { return (dispatch: Dispatch, getState: () => RootState) => {
bucket.methods.tokenContract().call().then(async (address: string) => { bucket.methods.tokenAddress().call().then(async (address: string) => {
const erc20Abi = IERC20Detailed.options.jsonInterface; const erc20Abi = IERC20Detailed.options.jsonInterface;
const erc20 = new config.web3!.eth.Contract(erc20Abi, address); const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
dispatch(loadingToken(address)); dispatch(loadingToken(address));

View File

@ -1,12 +1,12 @@
import { RootState } from '../reducers'; import { RootState } from '../reducers';
import ERC20Bucket from '../../../embarkArtifacts/contracts/ERC20Bucket'; import IERC20Detailed from '../embarkArtifacts/contracts/IERC20Detailed';
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed';
import { config } from "../config"; import { config } from "../config";
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import { newBucketContract } from "./bucket"; import { newBucketContract } from "./bucket";
import { sha3 } from "web3-utils"; import { sha3 } from "web3-utils";
import { recoverTypedSignature } from 'eth-sig-util'; import { recoverTypedSignature } from 'eth-sig-util';
import { Web3Type } from "../actions/web3"; import { Web3Type } from "../actions/web3";
import { KECCAK_EMPTY_STRING } from '../utils';
const sleep = (ms: number) => { const sleep = (ms: number) => {
return new Promise(resolve => { return new Promise(resolve => {
@ -91,31 +91,37 @@ const redeemDone = (txHash: string) => ({
txHash, txHash,
}); });
export const redeem = (bucketAddress: string, recipientAddress: string, code: string) => { export const redeem = (bucketAddress: string, recipientAddress: string, cleanCode: string) => {
return async (dispatch: Dispatch, getState: () => RootState) => { return async (dispatch: Dispatch, getState: () => RootState) => {
let finalCode;
if (cleanCode === "") {
finalCode = KECCAK_EMPTY_STRING;
} else {
finalCode = sha3(cleanCode);
}
dispatch(redeeming()); dispatch(redeeming());
const state = getState(); const state = getState();
const web3Type = state.web3.type; const web3Type = state.web3.type;
const bucketAddress = state.bucket.address;
const bucket = newBucketContract(bucketAddress); const bucket = newBucketContract(bucketAddress);
const codeHash = sha3(code);
const account = state.web3.account; const account = state.web3.account;
const block = await config.web3!.eth.getBlock("latest"); const block = await config.web3!.eth.getBlock("latest");
const message = { const message = {
receiver: state.web3.account, receiver: state.web3.account!,
code: codeHash, code: finalCode!,
blockNumber: block.number, blockNumber: block.number,
blockHash: block.hash, blockHash: block.hash,
}; };
//FIXME: is signer needed? //FIXME: is signer needed?
signRedeem(web3Type, bucketAddress, state.web3.account, message).then(async ({ sig, address }: SignRedeemResponse) => { signRedeem(web3Type, bucketAddress, state.web3.account!, message).then(async ({ sig, address }: SignRedeemResponse) => {
const recipient = state.bucket.recipient; const recipient = state.bucket.recipient!;
//FIXME: remove! hack to wait for the request screen to slide down //FIXME: remove! hack to wait for the request screen to slide down
await sleep(3000); await sleep(3000);
if (address.toLowerCase() != recipient.toLowerCase()) { if (address.toLowerCase() !== recipient.toLowerCase()) {
//FIXME: handle error //FIXME: handle error
dispatch(wrongSigner(recipient, address)); dispatch(wrongSigner(recipient, address));
return; return;
@ -123,13 +129,16 @@ export const redeem = (bucketAddress: string, recipientAddress: string, code: st
const redeem = bucket.methods.redeem(message, sig); const redeem = bucket.methods.redeem(message, sig);
const gas = await redeem.estimateGas(); const gas = await redeem.estimateGas();
redeem.send({ from: account, gas }).then(resp => { redeem.send({
from: account,
gas
}).then((resp: any) => {
dispatch(redeemDone(resp.transactionHash)); dispatch(redeemDone(resp.transactionHash));
}).catch(err => { }).catch((err: string) => {
console.error("redeem error: ", err); console.error("redeem error: ", err);
dispatch(redeemError(err)) dispatch(redeemError(err))
}); });
}).catch(err => { }).catch((err: string) => {
console.error("sign redeem error: ", err); console.error("sign redeem error: ", err);
dispatch(redeemError(err)) dispatch(redeemError(err))
}); });
@ -183,7 +192,7 @@ const signWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> =>
method: "eth_signTypedData_v3", method: "eth_signTypedData_v3",
params: [signer, JSON.stringify(data)], params: [signer, JSON.stringify(data)],
from: signer, from: signer,
}, (err, resp) => { }, (err: string, resp: any) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@ -201,14 +210,14 @@ const signWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> =>
const signWithKeycard = (signer: string, data: any): Promise<SignRedeemResponse> => { const signWithKeycard = (signer: string, data: any): Promise<SignRedeemResponse> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
(window as any).ethereum.send("keycard_signTypedData", [signer, JSON.stringify(data)]).then(resp => { (window as any).ethereum.send("keycard_signTypedData", [signer, JSON.stringify(data)]).then((resp: any) => {
const sig = resp.result; const sig = resp.result;
const address = recoverTypedSignature({ const address = recoverTypedSignature({
data, data,
sig sig
}); });
resolve({ sig, address }); resolve({ sig, address });
}).catch(err => { }).catch((err: string) => {
reject(err); reject(err);
}) })
}); });

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import ERC20BucketFactory from '../../../embarkArtifacts/contracts/ERC20BucketFactory'; import ERC20BucketFactory from '../embarkArtifacts/contracts/ERC20BucketFactory';
import { RootState } from '../reducers';
import { import {
shallowEqual, shallowEqual,
useSelector, useSelector,
useDispatch,
} from 'react-redux'; } from 'react-redux';
import { Web3Type } from "../actions/web3"; import { Web3Type } from "../actions/web3";
@ -21,7 +21,7 @@ const web3Type = (t: Web3Type) => {
} }
export default function(ownProps: any) { export default function(ownProps: any) {
const props = useSelector(state => { const props = useSelector((state: RootState) => {
return { return {
initialized: state.web3.networkID, initialized: state.web3.networkID,
networkID: state.web3.networkID, networkID: state.web3.networkID,
@ -31,11 +31,11 @@ export default function(ownProps: any) {
}, shallowEqual); }, shallowEqual);
if (props.error) { if (props.error) {
return `Error: ${props.error}`; return <>Error: {props.error}</>;
} }
if (!props.initialized) { if (!props.initialized) {
return "initializing..."; return <>initializing...</>;
} }
return <> return <>

View File

@ -1,16 +1,23 @@
import React from 'react'; import React from 'react';
class ErrorBoundary extends React.Component { interface Props {
constructor(props) { }
interface State {
hasError: boolean
}
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: any) {
super(props); super(props);
this.state = { hasError: false }; this.state = { hasError: false };
} }
static getDerivedStateFromError(error) { static getDerivedStateFromError(error: any) {
return { hasError: true }; return { hasError: true };
} }
componentDidCatch(error, errorInfo) { componentDidCatch(error: any, errorInfo: any) {
console.error(error); console.error(error);
} }

View File

@ -7,7 +7,5 @@ export default function() {
const dispatch = useDispatch(); const dispatch = useDispatch();
return <> return <>
<p>
</p>
</>; </>;
} }

View File

@ -13,7 +13,10 @@ import {
ERROR_LOADING_REDEEMABLE, ERROR_LOADING_REDEEMABLE,
ERROR_REDEEMABLE_NOT_FOUND, ERROR_REDEEMABLE_NOT_FOUND,
} from '../actions/bucket'; } from '../actions/bucket';
import { toBaseUnit } from "../utils"; import {
toBaseUnit,
KECCAK_EMPTY_STRING2,
} from "../utils";
import { import {
redeem, redeem,
RedeemErrors, RedeemErrors,
@ -21,8 +24,6 @@ import {
ERROR_WRONG_SIGNER, ERROR_WRONG_SIGNER,
} from '../actions/redeem'; } from '../actions/redeem';
const REDEEM_CODE = "hello world";
const buckerErrorMessage = (error: BucketErrors): string => { const buckerErrorMessage = (error: BucketErrors): string => {
switch (error.type) { switch (error.type) {
case ERROR_LOADING_REDEEMABLE: case ERROR_LOADING_REDEEMABLE:
@ -49,13 +50,23 @@ const redeemErrorMessage = (error: RedeemErrors): string => {
} }
} }
interface URLParams {
bucketAddress: string
recipientAddress: string
}
export default function(ownProps: any) { export default function(ownProps: any) {
const dispatch = useDispatch() const dispatch = useDispatch()
const match = useRouteMatch({
const match = useRouteMatch<URLParams>({
path: redeemPath, path: redeemPath,
exact: true, exact: true,
}); });
if (match === null) {
return null;
}
const bucketAddress = match.params.bucketAddress; const bucketAddress = match.params.bucketAddress;
const recipientAddress = match.params.recipientAddress; const recipientAddress = match.params.recipientAddress;
@ -78,30 +89,32 @@ export default function(ownProps: any) {
} }
}, shallowEqual); }, shallowEqual);
const emptyCode = props.codeHash === KECCAK_EMPTY_STRING2;
useEffect(() => { useEffect(() => {
dispatch(loadRedeemable(bucketAddress, recipientAddress)); dispatch(loadRedeemable(bucketAddress, recipientAddress));
}, [bucketAddress, recipientAddress]); }, [dispatch, bucketAddress, recipientAddress]);
if (props.error) { if (props.error) {
return `Error: ${buckerErrorMessage(props.error)}`; return <>Error: {buckerErrorMessage(props.error)}</>;
} }
if (props.loading) { if (props.loading) {
return "loading bucket..."; return <>loading bucket...</>;
} }
if (props.tokenSymbol === undefined || props.tokenDecimals === undefined) { if (props.tokenSymbol === undefined || props.tokenDecimals === undefined) {
return "loading token info..."; return <>loading token info...</>;
} }
const [displayAmount, roundedDisplayAmount] = toBaseUnit(props.amount, props.tokenDecimals, 2); const [displayAmount, roundedDisplayAmount] = toBaseUnit(props.amount!, props.tokenDecimals, 2);
return <> return <>
Bucket Address: {props.bucketAddress}<br /> Bucket Address: {props.bucketAddress}<br />
Recipient: {props.recipient}<br /> Recipient: {props.recipient}<br />
Amount: {props.amount}<br /> Amount: {props.amount}<br />
Expiration Time: {new Date(props.expirationTime * 1000).toLocaleDateString("default", {hour: "numeric", minute: "numeric"})}<br /> Expiration Time: {new Date(props.expirationTime! * 1000).toLocaleDateString("default", {hour: "numeric", minute: "numeric"})}<br />
Code Hash: {props.codeHash}<br /> Code Hash: {props.codeHash} {emptyCode ? "(empty string)" : ""}<br />
Token Address: {props.tokenAddress}<br /> Token Address: {props.tokenAddress}<br />
Token Symbol: {props.tokenSymbol}<br /> Token Symbol: {props.tokenSymbol}<br />
Token Decimals: {props.tokenDecimals}<br /> Token Decimals: {props.tokenDecimals}<br />
@ -112,7 +125,7 @@ export default function(ownProps: any) {
<br /><br /><br /> <br /><br /><br />
<button <button
disabled={props.redeeming} disabled={props.redeeming}
onClick={() => dispatch(redeem(bucketAddress, recipientAddress, REDEEM_CODE))}> onClick={() => dispatch(redeem(bucketAddress, recipientAddress, ""))}>
{props.redeeming ? "Redeeming..." : "Redeem"} {props.redeeming ? "Redeeming..." : "Redeem"}
</button> </button>
<br /> <br />

View File

@ -1,4 +1,3 @@
import EmbarkJS from 'Embark/EmbarkJS';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
@ -41,23 +40,21 @@ const store = createStore(
applyMiddleware(...middlewares), applyMiddleware(...middlewares),
); );
EmbarkJS.onReady(err => { store.dispatch<any>(initializeWeb3());
store.dispatch<any>(initializeWeb3());
ReactDOM.render( ReactDOM.render(
<ErrorBoundary> <ErrorBoundary>
<Provider store={store}> <Provider store={store}>
<App> <App>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<Switch> <Switch>
<Route exact path="/"><Home /></Route> <Route exact path="/"><Home /></Route>
<Route exact path={redeemPath}><Redeem /></Route> <Route exact path={redeemPath}><Redeem /></Route>
<Route render={() => "page not found"} /> <Route render={() => "page not found"} />
</Switch> </Switch>
</ConnectedRouter> </ConnectedRouter>
</App> </App>
</Provider> </Provider>
</ErrorBoundary>, </ErrorBoundary>,
document.getElementById("root") document.getElementById("root")
); );
});

View File

@ -1,5 +1,6 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router'; import { connectRouter } from 'connected-react-router';
import { History } from 'history';
import { import {
Web3State, Web3State,
web3Reducer, web3Reducer,
@ -19,7 +20,7 @@ export interface RootState {
redeem: RedeemState, redeem: RedeemState,
} }
export default function(history) { export default function(history: History) {
return combineReducers({ return combineReducers({
web3: web3Reducer, web3: web3Reducer,
router: connectRouter(history), router: connectRouter(history),

View File

@ -1,6 +1,9 @@
import Web3Utils from "web3-utils"; import { sha3 } from "web3-utils";
import BN from "bn.js";
const BN = Web3Utils.BN; // keccak256("")
export const KECCAK_EMPTY_STRING = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470";
export const KECCAK_EMPTY_STRING2 = sha3(KECCAK_EMPTY_STRING);
export const toBaseUnit = (fullAmount: string, decimalsSize: number, roundDecimals: number) => { export const toBaseUnit = (fullAmount: string, decimalsSize: number, roundDecimals: number) => {
const amount = new BN(fullAmount); const amount = new BN(fullAmount);

View File

@ -1,188 +0,0 @@
// some packages, plugins, and presets referenced/required in this webpack
// config are deps of embark and will be transitive dapp deps unless specified
// in the dapp's own package.json
// embark modifies process.env.NODE_PATH so that when running dapp scripts in
// embark's child processes, embark's own node_modules directory will be
// searched by node's require(); however, webpack and babel do not directly
// support NODE_PATH, so modules such as babel plugins and presets must be
// resolved with require.resolve(); that is only necessary if a plugin/preset
// is in embark's node_modules vs. the dapp's node_modules
const cloneDeep = require('lodash.clonedeep');
// const CompressionPlugin = require('compression-webpack-plugin');
const glob = require('glob');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const path = require('path');
const dappPath = process.env.DAPP_PATH;
const embarkPath = process.env.EMBARK_PATH;
const embarkAliases = require(path.join(dappPath, '.embark/embark-aliases.json'));
const embarkAssets = require(path.join(dappPath, '.embark/embark-assets.json'));
const embarkNodeModules = path.join(embarkPath, 'node_modules');
const embarkJson = require(path.join(dappPath, 'embark.json'));
const buildDir = path.join(dappPath, embarkJson.buildDir);
// it's important to `embark reset` if a pkg version is specified in
// embark.json and changed/removed later, otherwise pkg resolution may behave
// unexpectedly
let versions;
try {
versions = glob.sync(path.join(dappPath, '.embark/versions/*/*'));
} catch (e) {
versions = [];
}
const entry = Object.keys(embarkAssets)
.filter(key => key.match(/\.js$/))
.reduce((obj, key) => {
// webpack entry paths should start with './' if they're relative to the
// webpack context; embark.json "app" keys correspond to lists of .js
// source paths relative to the top-level dapp dir and may be missing the
// leading './'
obj[key] = embarkAssets[key]
.map(file => {
let file_path = file.path;
if (!file.path.match(/^\.\//)) {
file_path = './' + file_path;
}
return file_path;
});
return obj;
}, {});
function resolve(pkgName) {
if (Array.isArray(pkgName)) {
const _pkgName = pkgName[0];
pkgName[0] = require.resolve(_pkgName);
return pkgName;
}
return require.resolve(pkgName);
}
// base config
// -----------------------------------------------------------------------------
const base = {
context: dappPath,
entry: entry,
module: {
rules: [
{
test: /\.css$/,
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
},
{
test: /\.scss$/,
use: [{loader: 'style-loader'}, {loader: 'css-loader'}]
},
{
test: /\.(png|woff|woff2|eot|ttf|svg)$/,
loader: 'url-loader?limit=100000'
},
{
test: /\.(js|jsx|tsx|ts)$/,
loader: 'babel-loader',
exclude: /(node_modules|bower_components|\.embark[\\/]versions)/,
options: {
plugins: [
[
'babel-plugin-module-resolver', {
'alias': embarkAliases
}
],
[
'@babel/plugin-transform-runtime', {
corejs: 2,
useESModules: true
}
]
].map(resolve),
presets: [
[
'@babel/preset-env', {
modules: false,
targets: {
browsers: ['last 1 version', 'not dead', '> 0.2%']
}
}
],
'@babel/preset-react',
'@babel/preset-typescript'
].map(resolve)
}
}
]
},
output: {
filename: (chunkData) => chunkData.chunk.name,
// globalObject workaround for node-compatible UMD builds with webpack 4
// see: https://github.com/webpack/webpack/issues/6522#issuecomment-371120689
globalObject: 'typeof self !== \'undefined\' ? self : this',
libraryTarget: 'umd',
path: buildDir
},
plugins: [new HardSourceWebpackPlugin()],
// profiling and generating verbose stats increases build time; if stats
// are generated embark will write the output to:
// path.join(dappPath, '.embark/stats.[json,report]')
// to visualize the stats info in a browser run:
// npx webpack-bundle-analyzer .embark/stats.json <buildDir>
profile: true, stats: 'verbose',
resolve: {
alias: embarkAliases,
modules: [
...versions,
'node_modules',
embarkNodeModules
]
},
resolveLoader: {
modules: [
'node_modules',
embarkNodeModules
]
}
};
// typescript mods
// -----------------------------------------------------------------------------
base.resolve.extensions = [
// webpack defaults
// see: https://webpack.js.org/configuration/resolve/#resolve-extensions
'.wasm', '.mjs', '.js', '.json',
// typescript extensions
'.ts', '.tsx'
];
// development config
// -----------------------------------------------------------------------------
const development = cloneDeep(base);
// full source maps increase build time but are useful during dapp development
development.devtool = 'source-map';
development.mode = 'development';
// alternatively:
// development.mode = 'none';
development.name = 'development';
const devBabelLoader = development.module.rules[3];
devBabelLoader.options.compact = false;
// production config
// -----------------------------------------------------------------------------
const production = cloneDeep(base);
production.mode = 'production';
production.name = 'production';
// compression of webpack's JS output not enabled by default
// production.plugins.push(new CompressionPlugin());
// export a list of named configs
// -----------------------------------------------------------------------------
module.exports = [
development,
production
];

6389
yarn.lock

File diff suppressed because it is too large Load Diff