Merge branch 'master' of github.com:status-im/keycard-redeem
This commit is contained in:
commit
60133de7d1
|
@ -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>
|
|
@ -36,6 +36,19 @@ module.exports = {
|
|||
ERC20BucketFactory: {
|
||||
params: [],
|
||||
},
|
||||
ERC20Bucket: {
|
||||
params: [],
|
||||
proxyFor: "Bucket",
|
||||
deploy: false,
|
||||
},
|
||||
NFTBucketFactory: {
|
||||
params: [],
|
||||
},
|
||||
NFTBucket: {
|
||||
params: [],
|
||||
proxyFor: "Bucket",
|
||||
deploy: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ module.exports = {
|
|||
// A new DApp can be created from that template with:
|
||||
// embark new --template typescript
|
||||
// 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
|
||||
// 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
|
||||
};
|
||||
|
|
|
@ -67,14 +67,21 @@ abstract contract Bucket {
|
|||
function bucketType() virtual external returns (uint256);
|
||||
|
||||
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);
|
||||
|
||||
Redeemable storage redeemable = redeemables[recipient];
|
||||
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;
|
||||
|
||||
|
@ -109,15 +116,6 @@ abstract contract Bucket {
|
|||
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) {
|
||||
return keccak256(abi.encode(
|
||||
REDEEM_TYPEHASH,
|
||||
|
@ -155,9 +153,4 @@ abstract contract Bucket {
|
|||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
{
|
||||
"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/",
|
||||
"config": "config/",
|
||||
"versions": {
|
||||
|
@ -26,5 +20,5 @@
|
|||
"optimize-runs": 200
|
||||
}
|
||||
},
|
||||
"generationDir": "embarkArtifacts"
|
||||
"generationDir": "src/embarkArtifacts"
|
||||
}
|
||||
|
|
39
package.json
39
package.json
|
@ -3,28 +3,42 @@
|
|||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"test": "embark test"
|
||||
"test": "embark test",
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"esm": "^3.2.25",
|
||||
"eth-sig-util": "^2.5.3",
|
||||
"history": "^4.10.1",
|
||||
"minimist": "^1.2.3",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-redux": "^7.2.0",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"redux": "^4.0.5",
|
||||
"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": {
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"embark": "^5.3.0-nightly.7",
|
||||
"embark-geth": "^5.3.0-nightly.7",
|
||||
"embark-graph": "^5.3.0-nightly.7",
|
||||
|
@ -38,7 +52,18 @@
|
|||
"embarkjs-ipfs": "^5.3.0-nightly.4",
|
||||
"embarkjs-swarm": "^5.3.0-nightly.4",
|
||||
"embarkjs-web3": "^5.3.0-nightly.4",
|
||||
"embarkjs-whisper": "^5.3.0-nightly.4",
|
||||
"typescript": "^3.8.3"
|
||||
"embarkjs-whisper": "^5.3.0-nightly.4"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -195,4 +195,4 @@ async function run() {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
run();
|
||||
run();
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { RootState } from '../reducers';
|
||||
import ERC20Bucket from '../../../embarkArtifacts/contracts/ERC20Bucket';
|
||||
import IERC20Detailed from '../../../embarkArtifacts/contracts/IERC20Detailed';
|
||||
import ERC20Bucket from '../embarkArtifacts/contracts/ERC20Bucket';
|
||||
import IERC20Detailed from '../embarkArtifacts/contracts/IERC20Detailed';
|
||||
import { config } from "../config";
|
||||
import { Contract } from 'web3-eth-contract';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
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) => {
|
||||
dispatch(loadingRedeemable(bucketAddress, recipientAddress));
|
||||
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) => {
|
||||
const { recipient, amount, code } = result;
|
||||
const { recipient, data, code } = result;
|
||||
const amount = data;
|
||||
if (amount === "0") {
|
||||
dispatch(redeemableNotFound())
|
||||
return;
|
||||
|
@ -139,20 +139,21 @@ export const loadRedeemable = (bucketAddress: string, recipientAddress: string)
|
|||
|
||||
dispatch(redeemableLoaded(expirationTime, recipient, amount, code));
|
||||
dispatch<any>(loadToken(bucket))
|
||||
}).catch(err => {
|
||||
}).catch((err: string) => {
|
||||
dispatch(errorLoadingRedeemable(err))
|
||||
console.error("err: ", err)
|
||||
})
|
||||
}).catch(err => {
|
||||
}).catch((err: string) => {
|
||||
dispatch(errorLoadingRedeemable(`error loading expirationTime: ${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) => {
|
||||
bucket.methods.tokenContract().call().then(async (address: string) => {
|
||||
bucket.methods.tokenAddress().call().then(async (address: string) => {
|
||||
const erc20Abi = IERC20Detailed.options.jsonInterface;
|
||||
const erc20 = new config.web3!.eth.Contract(erc20Abi, address);
|
||||
dispatch(loadingToken(address));
|
|
@ -1,12 +1,12 @@
|
|||
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 { Dispatch } from 'redux';
|
||||
import { newBucketContract } from "./bucket";
|
||||
import { sha3 } from "web3-utils";
|
||||
import { recoverTypedSignature } from 'eth-sig-util';
|
||||
import { Web3Type } from "../actions/web3";
|
||||
import { KECCAK_EMPTY_STRING } from '../utils';
|
||||
|
||||
const sleep = (ms: number) => {
|
||||
return new Promise(resolve => {
|
||||
|
@ -91,31 +91,37 @@ const redeemDone = (txHash: string) => ({
|
|||
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) => {
|
||||
let finalCode;
|
||||
if (cleanCode === "") {
|
||||
finalCode = KECCAK_EMPTY_STRING;
|
||||
} else {
|
||||
finalCode = sha3(cleanCode);
|
||||
}
|
||||
|
||||
dispatch(redeeming());
|
||||
const state = getState();
|
||||
const web3Type = state.web3.type;
|
||||
const bucketAddress = state.bucket.address;
|
||||
|
||||
const bucket = newBucketContract(bucketAddress);
|
||||
const codeHash = sha3(code);
|
||||
const account = state.web3.account;
|
||||
const block = await config.web3!.eth.getBlock("latest");
|
||||
|
||||
const message = {
|
||||
receiver: state.web3.account,
|
||||
code: codeHash,
|
||||
receiver: state.web3.account!,
|
||||
code: finalCode!,
|
||||
blockNumber: block.number,
|
||||
blockHash: block.hash,
|
||||
};
|
||||
|
||||
//FIXME: is signer needed?
|
||||
signRedeem(web3Type, bucketAddress, state.web3.account, message).then(async ({ sig, address }: SignRedeemResponse) => {
|
||||
const recipient = state.bucket.recipient;
|
||||
signRedeem(web3Type, bucketAddress, state.web3.account!, message).then(async ({ sig, address }: SignRedeemResponse) => {
|
||||
const recipient = state.bucket.recipient!;
|
||||
//FIXME: remove! hack to wait for the request screen to slide down
|
||||
await sleep(3000);
|
||||
|
||||
if (address.toLowerCase() != recipient.toLowerCase()) {
|
||||
if (address.toLowerCase() !== recipient.toLowerCase()) {
|
||||
//FIXME: handle error
|
||||
dispatch(wrongSigner(recipient, address));
|
||||
return;
|
||||
|
@ -123,13 +129,16 @@ export const redeem = (bucketAddress: string, recipientAddress: string, code: st
|
|||
|
||||
const redeem = bucket.methods.redeem(message, sig);
|
||||
const gas = await redeem.estimateGas();
|
||||
redeem.send({ from: account, gas }).then(resp => {
|
||||
redeem.send({
|
||||
from: account,
|
||||
gas
|
||||
}).then((resp: any) => {
|
||||
dispatch(redeemDone(resp.transactionHash));
|
||||
}).catch(err => {
|
||||
}).catch((err: string) => {
|
||||
console.error("redeem error: ", err);
|
||||
dispatch(redeemError(err))
|
||||
});
|
||||
}).catch(err => {
|
||||
}).catch((err: string) => {
|
||||
console.error("sign redeem error: ", err);
|
||||
dispatch(redeemError(err))
|
||||
});
|
||||
|
@ -183,7 +192,7 @@ const signWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> =>
|
|||
method: "eth_signTypedData_v3",
|
||||
params: [signer, JSON.stringify(data)],
|
||||
from: signer,
|
||||
}, (err, resp) => {
|
||||
}, (err: string, resp: any) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
|
@ -201,14 +210,14 @@ const signWithWeb3 = (signer: string, data: any): Promise<SignRedeemResponse> =>
|
|||
|
||||
const signWithKeycard = (signer: string, data: any): Promise<SignRedeemResponse> => {
|
||||
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 address = recoverTypedSignature({
|
||||
data,
|
||||
sig
|
||||
});
|
||||
resolve({ sig, address });
|
||||
}).catch(err => {
|
||||
}).catch((err: string) => {
|
||||
reject(err);
|
||||
})
|
||||
});
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import ERC20BucketFactory from '../../../embarkArtifacts/contracts/ERC20BucketFactory';
|
||||
import ERC20BucketFactory from '../embarkArtifacts/contracts/ERC20BucketFactory';
|
||||
import { RootState } from '../reducers';
|
||||
import {
|
||||
shallowEqual,
|
||||
useSelector,
|
||||
useDispatch,
|
||||
} from 'react-redux';
|
||||
import { Web3Type } from "../actions/web3";
|
||||
|
||||
|
@ -21,7 +21,7 @@ const web3Type = (t: Web3Type) => {
|
|||
}
|
||||
|
||||
export default function(ownProps: any) {
|
||||
const props = useSelector(state => {
|
||||
const props = useSelector((state: RootState) => {
|
||||
return {
|
||||
initialized: state.web3.networkID,
|
||||
networkID: state.web3.networkID,
|
||||
|
@ -31,11 +31,11 @@ export default function(ownProps: any) {
|
|||
}, shallowEqual);
|
||||
|
||||
if (props.error) {
|
||||
return `Error: ${props.error}`;
|
||||
return <>Error: {props.error}</>;
|
||||
}
|
||||
|
||||
if (!props.initialized) {
|
||||
return "initializing...";
|
||||
return <>initializing...</>;
|
||||
}
|
||||
|
||||
return <>
|
|
@ -1,16 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
constructor(props) {
|
||||
interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<Props, State> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
static getDerivedStateFromError(error: any) {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
componentDidCatch(error, errorInfo) {
|
||||
componentDidCatch(error: any, errorInfo: any) {
|
||||
console.error(error);
|
||||
}
|
||||
|
|
@ -7,7 +7,5 @@ export default function() {
|
|||
const dispatch = useDispatch();
|
||||
|
||||
return <>
|
||||
<p>
|
||||
</p>
|
||||
</>;
|
||||
}
|
|
@ -13,7 +13,10 @@ import {
|
|||
ERROR_LOADING_REDEEMABLE,
|
||||
ERROR_REDEEMABLE_NOT_FOUND,
|
||||
} from '../actions/bucket';
|
||||
import { toBaseUnit } from "../utils";
|
||||
import {
|
||||
toBaseUnit,
|
||||
KECCAK_EMPTY_STRING2,
|
||||
} from "../utils";
|
||||
import {
|
||||
redeem,
|
||||
RedeemErrors,
|
||||
|
@ -21,8 +24,6 @@ import {
|
|||
ERROR_WRONG_SIGNER,
|
||||
} from '../actions/redeem';
|
||||
|
||||
const REDEEM_CODE = "hello world";
|
||||
|
||||
const buckerErrorMessage = (error: BucketErrors): string => {
|
||||
switch (error.type) {
|
||||
case ERROR_LOADING_REDEEMABLE:
|
||||
|
@ -49,13 +50,23 @@ const redeemErrorMessage = (error: RedeemErrors): string => {
|
|||
}
|
||||
}
|
||||
|
||||
interface URLParams {
|
||||
bucketAddress: string
|
||||
recipientAddress: string
|
||||
}
|
||||
|
||||
export default function(ownProps: any) {
|
||||
const dispatch = useDispatch()
|
||||
const match = useRouteMatch({
|
||||
|
||||
const match = useRouteMatch<URLParams>({
|
||||
path: redeemPath,
|
||||
exact: true,
|
||||
});
|
||||
|
||||
if (match === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bucketAddress = match.params.bucketAddress;
|
||||
const recipientAddress = match.params.recipientAddress;
|
||||
|
||||
|
@ -78,30 +89,32 @@ export default function(ownProps: any) {
|
|||
}
|
||||
}, shallowEqual);
|
||||
|
||||
const emptyCode = props.codeHash === KECCAK_EMPTY_STRING2;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(loadRedeemable(bucketAddress, recipientAddress));
|
||||
}, [bucketAddress, recipientAddress]);
|
||||
}, [dispatch, bucketAddress, recipientAddress]);
|
||||
|
||||
if (props.error) {
|
||||
return `Error: ${buckerErrorMessage(props.error)}`;
|
||||
return <>Error: {buckerErrorMessage(props.error)}</>;
|
||||
}
|
||||
|
||||
if (props.loading) {
|
||||
return "loading bucket...";
|
||||
return <>loading bucket...</>;
|
||||
}
|
||||
|
||||
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 <>
|
||||
Bucket Address: {props.bucketAddress}<br />
|
||||
Recipient: {props.recipient}<br />
|
||||
Amount: {props.amount}<br />
|
||||
Expiration Time: {new Date(props.expirationTime * 1000).toLocaleDateString("default", {hour: "numeric", minute: "numeric"})}<br />
|
||||
Code Hash: {props.codeHash}<br />
|
||||
Expiration Time: {new Date(props.expirationTime! * 1000).toLocaleDateString("default", {hour: "numeric", minute: "numeric"})}<br />
|
||||
Code Hash: {props.codeHash} {emptyCode ? "(empty string)" : ""}<br />
|
||||
Token Address: {props.tokenAddress}<br />
|
||||
Token Symbol: {props.tokenSymbol}<br />
|
||||
Token Decimals: {props.tokenDecimals}<br />
|
||||
|
@ -112,7 +125,7 @@ export default function(ownProps: any) {
|
|||
<br /><br /><br />
|
||||
<button
|
||||
disabled={props.redeeming}
|
||||
onClick={() => dispatch(redeem(bucketAddress, recipientAddress, REDEEM_CODE))}>
|
||||
onClick={() => dispatch(redeem(bucketAddress, recipientAddress, ""))}>
|
||||
{props.redeeming ? "Redeeming..." : "Redeem"}
|
||||
</button>
|
||||
<br />
|
|
@ -1,4 +1,3 @@
|
|||
import EmbarkJS from 'Embark/EmbarkJS';
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
|
@ -41,23 +40,21 @@ const store = createStore(
|
|||
applyMiddleware(...middlewares),
|
||||
);
|
||||
|
||||
EmbarkJS.onReady(err => {
|
||||
store.dispatch<any>(initializeWeb3());
|
||||
store.dispatch<any>(initializeWeb3());
|
||||
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<Provider store={store}>
|
||||
<App>
|
||||
<ConnectedRouter history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/"><Home /></Route>
|
||||
<Route exact path={redeemPath}><Redeem /></Route>
|
||||
<Route render={() => "page not found"} />
|
||||
</Switch>
|
||||
</ConnectedRouter>
|
||||
</App>
|
||||
</Provider>
|
||||
</ErrorBoundary>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
});
|
||||
ReactDOM.render(
|
||||
<ErrorBoundary>
|
||||
<Provider store={store}>
|
||||
<App>
|
||||
<ConnectedRouter history={history}>
|
||||
<Switch>
|
||||
<Route exact path="/"><Home /></Route>
|
||||
<Route exact path={redeemPath}><Redeem /></Route>
|
||||
<Route render={() => "page not found"} />
|
||||
</Switch>
|
||||
</ConnectedRouter>
|
||||
</App>
|
||||
</Provider>
|
||||
</ErrorBoundary>,
|
||||
document.getElementById("root")
|
||||
);
|
|
@ -1,5 +1,6 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import { connectRouter } from 'connected-react-router';
|
||||
import { History } from 'history';
|
||||
import {
|
||||
Web3State,
|
||||
web3Reducer,
|
||||
|
@ -19,7 +20,7 @@ export interface RootState {
|
|||
redeem: RedeemState,
|
||||
}
|
||||
|
||||
export default function(history) {
|
||||
export default function(history: History) {
|
||||
return combineReducers({
|
||||
web3: web3Reducer,
|
||||
router: connectRouter(history),
|
|
@ -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) => {
|
||||
const amount = new BN(fullAmount);
|
|
@ -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
|
||||
];
|
Loading…
Reference in New Issue