Completed callgasrelayed component

This commit is contained in:
Richard Ramos 2018-08-06 22:30:16 -04:00
parent f61e98f75e
commit d3d9710a02
5 changed files with 425 additions and 279 deletions

View File

@ -22,16 +22,6 @@ class AccountBalance extends React.Component {
updateBalances(ev){
if(ev) ev.preventDefault();
web3.eth.getBalance(this.props.address)
.then(eth => {
this.setState({eth});
});
this.props.RND.methods.balanceOf(this.props.address)
.call()
.then(rnd => {
this.setState({rnd});
});
}
sendEther(ev){
@ -56,8 +46,8 @@ class AccountBalance extends React.Component {
}
render(){
const rnd = web3.utils.fromWei(this.state.rnd, "ether");
const eth = web3.utils.fromWei(this.state.eth, "ether");
const rnd = 1;
const eth =2;
return <div>
<h3>{this.props.name}</h3>

View File

@ -1,4 +1,5 @@
import React, {Component, Fragment} from 'react';
import CallGasRelayed from './callgasrelayed';
import Divider from '@material-ui/core/Divider';
import EmbarkJS from 'Embark/EmbarkJS';
import IdentityFactory from 'Embark/contracts/IdentityFactory';
@ -18,7 +19,8 @@ class Body extends Component {
super(props);
this.state = {
tab: 0,
identityAddress: null
identityAddress: null,
nonce: '0'
};
}
@ -39,13 +41,18 @@ class Body extends Component {
this.setState({tab});
};
updateNonce = (newNonce) => {
this.setState({nonce: newNonce});
}
newIdentity = (cb) => {
let toSend = IdentityFactory.methods['createIdentity()']();
toSend.estimateGas()
.then(estimatedGas => {
return toSend.send({gas: estimatedGas + 100000});
return toSend.send({gas: estimatedGas + 1000000});
})
.then((receipt) => {
console.log(receipt);
const instance = receipt.events.IdentityCreated.returnValues.instance;
this.setState({identityAddress: instance});
cb();
@ -53,7 +60,7 @@ class Body extends Component {
}
render(){
const {tab, identityAddress} = this.state;
const {tab, identityAddress, nonce} = this.state;
return <Fragment>
<Tabs value={tab} onChange={this.handleChange}>
@ -61,22 +68,24 @@ class Body extends Component {
<Tab label="Approve and Call" />
<Tab label="Deploy" />
</Tabs>
{tab === 0 && <TabContainer>One</TabContainer>}
{tab === 1 && <TabContainer>Item Two</TabContainer>}
{tab === 2 && <TabContainer>Item Three</TabContainer>}
{tab === 0 && <Container><CallGasRelayed nonce={nonce} identityAddress={identityAddress} /></Container>}
{tab === 1 && <Container>Item Two</Container>}
{tab === 2 && <Container>Item Three</Container>}
<Divider />
<Status identityCreationFunction={this.newIdentity} identityAddress={identityAddress} />
<Container>
<Status identityCreationFunction={this.newIdentity} nonceUpdateFunction={this.updateNonce} nonce={nonce} identityAddress={identityAddress} />
</Container>
</Fragment>;
}
}
function TabContainer(props) {
function Container(props) {
return <Typography component="div" style={{padding: 8 * 3}}>
{props.children}
</Typography>;
}
TabContainer.propTypes = {
Container.propTypes = {
children: PropTypes.node.isRequired
};

View File

@ -1,269 +1,392 @@
import {Alert, Button, Col, ControlLabel, Form, FormControl, Grid, HelpBlock, InputGroup, Row} from 'react-bootstrap';
import AccountBalance from './accountBalance';
import React from 'react';
import Web3 from 'web3';
import React, {Component} from 'react';
import Button from '@material-ui/core/Button';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import CardHeader from '@material-ui/core/CardHeader';
import EmbarkJS from 'Embark/EmbarkJS';
import ErrorIcon from '@material-ui/icons/Error';
import Grid from '@material-ui/core/Grid';
import IdentityGasRelay from 'Embark/contracts/IdentityGasRelay';
import PropTypes from 'prop-types';
import STT from 'Embark/contracts/STT';
import SnackbarContent from '@material-ui/core/SnackbarContent';
import TestContract from 'Embark/contracts/TestContract';
import TextField from '@material-ui/core/TextField';
import Typography from '@material-ui/core/Typography';
import classNames from 'classnames';
import config from '../config';
import web3 from 'Embark/web3';
import {withStyles} from '@material-ui/core/styles';
const styles = theme => ({
root: {
width: '100%',
backgroundColor: theme.palette.background.paper
},
card: {
marginBottom: theme.spacing.unit * 3
}
});
class CallGasRelayed extends React.Component {
window.TestContract = TestContract;
class CallGasRelayed extends Component {
constructor(props) {
constructor(props){
super(props);
this.state = {
account: '',
address: this.props.IdentityGasRelay.options.address,
topic: '0x4964656e',
to: '0x0000000000000000000000000000000000000000',
value: 0,
data: '0x00',
nonce: 0,
gasPrice: 0,
gasLimit: 0,
gasToken: "0x0000000000000000000000000000000000000000",
signature: '',
symKey: '0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b',
kid: null,
skid: null,
msgSent: '',
payload: '',
messages: [],
message: '',
web3W: null,
errorMessage: ''
transactionError: '',
messagingError: '',
submitting: false
};
}
componentDidMount(){
__embarkContext.execWhenReady(async () => {
EmbarkJS.onReady(() => {
web3.shh.addSymKey(config.relaySymKey)
.then((skid) => {
this.setState({skid});
let web3W = new Web3('ws://localhost:8546');
const subsOptions = {
topic: [this.state.topic],
symKeyID: skid
};
let accounts = await this.props.web3.eth.getAccounts();
this.setState({
account: accounts[0]
});
let _skid = await web3W.shh.addSymKey(this.state.symKey);
let _kid = await web3W.shh.newKeyPair();
this.setState({
kid: _kid,
skid: _skid,
whisper: web3W
});
web3W.shh.subscribe('messages', {
"privateKeyID": _kid,
"ttl": 20,
"minPow": 0.8,
"powTime": 1000
}, (error, message, subscription) => {
if(error) {
console.log(error);
this.setState({errorMessage: error.message});
EmbarkJS.Messages.listenTo(subsOptions, (error, message) => {
if(error){
console.error(error);
} else {
this.state.messages.push(this.props.web3.utils.hexToAscii(message.payload));
this.setState({messages: this.state.messages});
console.groupCollapsed("Message Sent");
console.log(message);
console.groupEnd();
}
this.setState({submitting: false});
});
EmbarkJS.Messages.listenTo({usePrivateKey: true}, (error, message) => {
if(error){
console.error(error);
} else {
this.setState({message: JSON.stringify(message.data, null, " ")});
}
});
});
});
}
handleChange(e, name){
const newState = {};
newState[name] = e.target.value;
this.setState(newState);
}
handleChange = name => event => {
this.setState({
[name]: event.target.value
});
};
sendMessage(e){
e.preventDefault();
sign = (event) => {
if(event) event.preventDefault();
this.setState({
messages: [],
errorMessage: ''
msgSent: false,
payload: '',
message: '',
transactionError: ''
});
IdentityGasRelay.options.address = this.props.identityAddress;
try {
IdentityGasRelay.methods.callGasRelayHash(
this.state.to,
this.state.value,
web3.utils.soliditySha3({t: 'bytes', v: this.state.data}),
this.props.nonce,
this.state.gasPrice,
this.state.gasLimit,
this.state.gasToken
)
.call()
.then(message => {
return web3.eth.sign(message, web3.eth.defaultAccount);
})
.then(signature => {
this.setState({signature});
});
} catch(error){
this.setState({transactionError: error.message});
}
}
sendMessage = event => {
event.preventDefault();
this.setState({
message: '',
messagingError: '',
submitting: true
});
try {
let jsonAbi = this.props.IdentityGasRelay._jsonInterface.filter(x => x.name == "callGasRelayed")[0];
let funCall = this.props.web3.eth.abi.encodeFunctionCall(jsonAbi, [
let jsonAbi = IdentityGasRelay._jsonInterface.filter(x => x.name == "callGasRelayed")[0];
let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [
this.state.to,
this.state.value,
this.state.data,
this.state.nonce,
this.props.nonce,
this.state.gasPrice,
this.state.gasLimit,
this.state.gasToken,
this.state.signature
]);
let msgObj = {
symKeyID: this.state.skid,
sig: this.state.kid,
const sendOptions = {
ttl: 1000,
powTarget: 1,
powTime: 20,
topic: this.state.topic,
payload: this.props.web3.utils.toHex({
'address': this.state.address,
symKeyID: this.state.skid,
data: {
'address': this.props.identityAddress,
'encodedFunctionCall': funCall
})
}
};
EmbarkJS.Messages.sendMessage(sendOptions);
console.log(msgObj);
this.props.web3.shh.post(msgObj)
.then((err, result) => {
console.log(result);
console.log(err);
this.setState({msgSent: result, payload: msgObj.payload});
});
} catch(error){
console.error(error);
this.setState({errorMessage: error.message});
this.setState({messagingError: error.message, submitting: false});
}
}
async sign(ev){
ev.preventDefault();
this.setState({
msgSent: false,
payload: '',
messages: [],
errorMessage: ''
});
try {
let message = await this.props.IdentityGasRelay.methods.callGasRelayHash(
this.state.to,
this.state.value,
this.props.web3.utils.soliditySha3({t: 'bytes', v: this.state.data}),
this.state.nonce,
this.state.gasPrice,
this.state.gasLimit,
this.state.gasToken
).call();
let accounts = await this.props.web3.eth.getAccounts();
let _signature = await this.props.web3.eth.sign(message, accounts[0]);
this.setState({signature: _signature});
} catch(error){
console.error(error);
this.setState({errorMessage: error.message});
testContractDataSend = () => {
let jsonAbi = TestContract._jsonInterface.filter(x => x.name == "test")[0];
let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, []);
this.setState({data: funCall, to: TestContract.options.address});
}
testContractDataCall = () => {
TestContract.methods.val().call().then(value => this.setState({message: "TestContract.val(): " + value}));
}
render(){
return (<Grid>
{
this.state.errorMessage != '' && <React.Fragment><Alert bsStyle="danger">{this.state.errorMessage}</Alert></React.Fragment>
}
<Form>
<Row>
<Col md={3}>
<AccountBalance name="Identity" address={this.state.address} web3={this.props.web3} RND={this.props.RND} />
</Col>
<Col md={3}>
<AccountBalance name="To" address={this.state.to} web3={this.props.web3} RND={this.props.RND} />
</Col>
<Col md={3}>
<AccountBalance name="Gas Relayer Node" address={this.state.account} web3={this.props.web3} RND={this.props.RND} />
</Col>
</Row>
<Row>
<Col md={9}>
<ControlLabel>Identity Address</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="text" placeholder="Address" defaultValue={this.state.address} onChange={(ev) => this.handleChange(ev, 'address')} />
</InputGroup>
</Col>
</Row>
<Row>
<Col md={4}>
<ControlLabel>To</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="text" defaultValue={this.state.to} onChange={(ev) => this.handleChange(ev, 'to')} />
</InputGroup>
</Col>
<Col md={1}>
<ControlLabel>Value</ControlLabel>
<FormControl type="string" defaultValue={this.state.value} onChange={(ev) => this.handleChange(ev, 'value')} />
</Col>
<Col md={4}>
<ControlLabel>Data</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="string" defaultValue={this.state.data} onChange={(ev) => this.handleChange(ev, 'data')} />
</InputGroup>
</Col>
</Row>
<Row>
<Col md={1}>
<ControlLabel>Nonce</ControlLabel>
<FormControl type="string" defaultValue={this.state.nonce} onChange={(ev) => this.handleChange(ev, 'nonce')} />
</Col>
<Col md={1}>
<ControlLabel>Gas Price in Tokens</ControlLabel>
<FormControl type="string" defaultValue={this.state.gasPrice} onChange={(ev) => this.handleChange(ev, 'gasPrice')} />
</Col>
<Col md={1}>
<ControlLabel>Gas Limit in Ether</ControlLabel>
<FormControl type="string" defaultValue={this.state.gasLimit} onChange={(ev) => this.handleChange(ev, 'gasLimit')} />
</Col>
<Col md={6}>
<ControlLabel>Gas Token</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="text" defaultValue={this.state.gasToken} onChange={(ev) => this.handleChange(ev, 'gasToken')} />
</InputGroup>
<HelpBlock>RND: {this.props.RND.options.address}</HelpBlock>
<HelpBlock>ETH: 0x0000000000000000000000000000000000000000</HelpBlock>
</Col>
</Row>
<Row>
<Col md={4}>
<Button bsStyle="primary" onClick={(ev) => this.sign(ev)}>1. Sign Message</Button>
</Col>
<Col md={5}>
<b>Signature: </b>{this.state.signature}
</Col>
</Row>
<Row>
<Col md={7}>
<ControlLabel>Symmetric Key</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="text" placeholder="Sym Key" defaultValue={this.state.symKey} />
</InputGroup>
</Col>
<Col md={2}>
<ControlLabel>Topic</ControlLabel>
<InputGroup>
<InputGroup.Addon>0x</InputGroup.Addon>
<FormControl type="text" readOnly={true} defaultValue={this.state.topic} onChange={(ev) => this.handleChange(ev, 'topic')} />
</InputGroup>
</Col>
</Row>
<Row>
<Col md={4}>
<Button bsStyle="primary" disabled={this.state.signature == ""} onClick={(ev) => this.sendMessage(ev)}>2. Send via whisper</Button>
</Col>
<Col md={5}>
{this.state.msgSent}
</Col>
</Row>
<Row>
<h3>Whisper Messages</h3>
{
this.state.messages.map((msg, i) => <p key={i}>{msg}</p>)
}
</Row>
</Form>
const {classes} = this.props;
return <div>
{ this.state.transactionError && <MySnackbarContentWrapper variant="error" message={this.state.transactionError} /> }
<Card className={classes.card}>
<CardHeader title="1. Transaction Data" />
<CardContent>
<form noValidate autoComplete="off">
<Grid container spacing={24}>
<Grid item xs={5}>
<TextField
id="to"
label="To"
value={this.state.to}
onChange={this.handleChange('to')}
margin="normal"
fullWidth
/>
</Grid>
);
}
}
<Grid item xs={2}>
<TextField
id="value"
label="Value"
value={this.state.value}
onChange={this.handleChange('value')}
margin="normal"
fullWidth
/>
</Grid>
<Grid item xs={5}>
<TextField
id="data"
label="Data"
value={this.state.data}
onChange={this.handleChange('data')}
margin="normal"
fullWidth
/>
</Grid>
<Grid item xs={2}>
<TextField
id="nonce"
label="Nonce"
value={this.props.nonce}
margin="normal"
fullWidth
InputProps={{
readOnly: true
}}
/>
</Grid>
<Grid item xs={6}>
<TextField
id="gasToken"
label="Gas Token"
value={this.state.gasToken}
onChange={this.handleChange('gasToken')}
margin="normal"
fullWidth
select
SelectProps={{
native: true
}}
>
<option key={STT.options.address} value={STT.options.address}>
{STT.options.address} (STT)
</option>
<option key="0x0000000000000000000000000000000000000000" value="0x0000000000000000000000000000000000000000">
0x0000000000000000000000000000000000000000 (ETH)
</option>
</TextField>
</Grid>
<Grid item xs={2}>
<TextField
id="gasPrice"
label="Gas Price"
value={this.state.gasPrice}
onChange={this.handleChange('gasPrice')}
margin="normal"
fullWidth
/>
</Grid>
<Grid item xs={2}>
<TextField
id="gasLimit"
label="Gas Limit"
value={this.state.gasLimit}
onChange={this.handleChange('gasLimit')}
margin="normal"
fullWidth
/>
</Grid>
</Grid>
</form>
</CardContent>
<CardActions>
<Button color="primary" onClick={this.sign}>
Sign Message
</Button>
<Button size="small" onClick={this.testContractDataSend}>TestContract.methods.test().send()</Button>
<Button size="small" onClick={this.testContractDataCall}>TestContract.methods.test().call()</Button>
export default CallGasRelayed;
</CardActions>
</Card>
{ this.state.messagingError && <MySnackbarContentWrapper variant="error" message={this.state.messagingError} /> }
<Card className={classes.card}>
<CardHeader title="2 Message" />
<CardContent>
<TextField
id="signature"
label="Signed Message"
value={this.state.signature}
margin="normal"
fullWidth
InputProps={{
readOnly: true
}}
/>
<TextField
id="symKey"
label="Symmetric Key"
value={config.relaySymKey}
margin="normal"
fullWidth
InputProps={{
readOnly: true
}}
/>
<TextField
id="topic"
label="Whisper Topic"
value={this.state.topic}
margin="normal"
InputProps={{
readOnly: true
}}
/>
</CardContent>
<CardActions>
<Button size="small" color="primary" onClick={this.sendMessage} disabled={this.state.submitting}>
Send Message
</Button>
</CardActions>
</Card>
<Card className={classes.card}>
<CardContent>
<Typography>
Message Received:
</Typography>
<pre>{this.state.message}</pre>
</CardContent>
</Card>
</div>;
}
}
CallGasRelayed.propTypes = {
classes: PropTypes.object.isRequired,
nonce: PropTypes.string.isRequired,
identityAddress: PropTypes.string
};
const variantIcon = {
error: ErrorIcon
};
const styles1 = theme => ({
error: {
backgroundColor: theme.palette.error.dark
},
icon: {
fontSize: 20
},
iconVariant: {
opacity: 0.9,
marginRight: theme.spacing.unit
},
message: {
display: 'flex',
alignItems: 'center'
}
});
function MySnackbarContent(props) {
const {classes, className, message, variant, ...other} = props;
const Icon = variantIcon[variant];
return (
<SnackbarContent
className={classNames(classes[variant], className)}
aria-describedby="client-snackbar"
message={
<span id="client-snackbar" className={classes.message}>
<Icon className={classNames(classes.icon, classes.iconVariant)} />
{message}
</span>
}
{...other}
/>
);
}
MySnackbarContent.propTypes = {
classes: PropTypes.object.isRequired,
className: PropTypes.string,
message: PropTypes.node,
onClose: PropTypes.func,
variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired
};
const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent);
export default withStyles(styles)(CallGasRelayed);

View File

@ -4,12 +4,14 @@ import BalanceIcon from '@material-ui/icons/AccountBalance';
import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import EmbarkJS from 'Embark/EmbarkJS';
import Identity from 'Embark/contracts/Identity';
import KeyIcon from '@material-ui/icons/VpnKey';
import LinearProgress from '@material-ui/core/LinearProgress';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import NumberIcon from '@material-ui/icons/ConfirmationNumber';
import PropTypes from 'prop-types';
import RefreshIcon from '@material-ui/icons/Refresh';
import STT from 'Embark/contracts/STT';
@ -17,7 +19,7 @@ import Typography from '@material-ui/core/Typography';
import config from '../config';
import web3 from 'Embark/web3';
import {withStyles} from '@material-ui/core/styles';
window.Id = Identity;
const styles = theme => ({
button: {
marginRight: theme.spacing.unit * 2
@ -71,18 +73,21 @@ class Status extends Component {
relayerAddress: config.relayAccount
});
this.updateBalances();
web3.eth.subscribe('newBlockHeaders')
.on("data", (blockHeader) => {
if(blockHeader.number){
this.setState({block: blockHeader.number});
this.updateBalances();
}
});
setInterval(() => {
this.getBlock();
}, 5000);
});
}
updateBalances = () => {
getBlock = () => {
web3.eth.getBlock('latest')
.then((block) => {
this.setState({block: block.number});
this.readChain();
});
}
readChain = () => {
if(this.props.identityAddress){
web3.eth.getBalance(this.props.identityAddress)
.then(identityEthBalance => {
@ -94,6 +99,13 @@ class Status extends Component {
.then(identitySTTBalance => {
this.setState({identitySTTBalance: web3.utils.fromWei(identitySTTBalance, 'ether')});
});
Identity.options.address = this.props.identityAddress;
Identity.methods.nonce()
.call()
.then((nonce) => {
this.props.nonceUpdateFunction(nonce);
});
}
web3.eth.getBalance(this.state.relayerAddress)
@ -115,7 +127,7 @@ class Status extends Component {
submitState.generateSTT = true;
this.setState({submitState});
let toSend = STT.methods.generateTokens(this.props.identityAddress, web3.utils.toWei(5000, 'ether'));
let toSend = STT.methods.generateTokens(this.props.identityAddress, web3.utils.toWei('5000', 'ether'));
toSend.estimateGas()
.then(estimatedGas => {
return toSend.send({gas: estimatedGas + 10000});
@ -150,7 +162,7 @@ class Status extends Component {
submitState.etherSend = true;
this.setState({submitState});
web3.eth.sendTransaction({from: web3.eth.defaultAccount, to: this.state.relayerAddress, value: web3.utils.toWei(1, "ether")})
web3.eth.sendTransaction({from: web3.eth.defaultAccount, to: this.state.relayerAddress, value: web3.utils.toWei('1', "ether")})
.then((receipt) => {
console.log(receipt);
submitState = this.state.submitState;
@ -160,7 +172,7 @@ class Status extends Component {
}
render(){
const {classes, identityAddress} = this.props;
const {classes, identityAddress, nonce} = this.props;
const {identityEthBalance, relayerAddress, relayerEthBalance, identitySTTBalance, relayerSTTBalance, submitState, block} = this.state;
return <div className={classes.container}>
@ -190,6 +202,15 @@ class Status extends Component {
secondary="Address"
/>
</ListItem>
<ListItem className={classes.root}>
<ListItemIcon>
<NumberIcon />
</ListItemIcon>
<ListItemText
primary={nonce}
secondary="Nonce"
/>
</ListItem>
<ListItem className={classes.root}>
<ListItemIcon>
<BalanceIcon />
@ -252,7 +273,9 @@ class Status extends Component {
Status.propTypes = {
classes: PropTypes.object.isRequired,
identityAddress: PropTypes.string,
identityCreationFunction: PropTypes.func.isRequired
nonce: PropTypes.string.isRequired,
identityCreationFunction: PropTypes.func.isRequired,
nonceUpdateFunction: PropTypes.func.isRequired
};
export default withStyles(styles)(Status);

View File

@ -1,5 +1,6 @@
const config = {
"relayAccount": "0x3c72082cbd10a874d673f25e0d48b72d294b5368"
"relayAccount": "0x91b3c570ace45716010e162cbbc1b27cd1a10b65",
"relaySymKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b"
};
export default config;