simplify ui

This commit is contained in:
Ricardo Guilherme Schmidt 2019-03-29 06:42:10 -03:00
parent e738876d2b
commit 1648936e2e
No known key found for this signature in database
GPG Key ID: BFB3F5C8ED618A94
12 changed files with 622 additions and 2321 deletions

View File

@ -0,0 +1,46 @@
import EmbarkJS from 'Embark/EmbarkJS';
import MultiSigWallet from 'Embark/contracts/MultiSigWallet';
import React from 'react';
import {Tabs, Tab} from 'react-bootstrap';
import MSWTransactionTable from './multisigwallet/transaction-table';
import MSWOwnerTable from './multisigwallet/owner-table';
class MultiSigWalletUI extends React.Component {
constructor(props) {
super(props);
this.state = {
account: props.account,
mswInstance: props.instance,
isOwner: props.isOwner,
activeKey: 1
}
}
_addToLog(txt){
console.log(txt);
}
handleSelect(key) {
this.setState({ activeKey: key });
}
render() {
return (
<React.Fragment>
<h2>{this.state.mswInstance._address}</h2>
<Tabs id="multisig-controls">
<Tab eventKey={1} title="Transactions">
<MSWTransactionTable instance={this.state.mswInstance} account={this.state.account} control={this.state.isOwner} />
</Tab>
<Tab eventKey={2} title="Owners">
<MSWOwnerTable instance={this.state.mswInstance} account={this.state.account} control={this.state.isOwner} />
</Tab>
</Tabs>
</React.Fragment>
);
}
}
export default MultiSigWalletUI;

View File

@ -0,0 +1,73 @@
import React from 'react';
import { Button } from 'react-bootstrap';
class MSWConfirmation extends React.Component {
constructor(props){
super(props);
this.state = {
control: props.control,
mswInstance: props.instance,
transactionId: props.id,
account: props.account,
accountConfirmed: false,
confirmations: 0,
required: 0,
error: null,
mined: null
};
this.load()
}
load() {
this.state.mswInstance.methods.required().call().then((required) => {
this.setState({required: required});
})
this.state.mswInstance.methods.getConfirmationCount(this.state.transactionId).call().then((confirmations) => {
this.setState({confirmations: confirmations});
})
if(this.state.control){
this.state.mswInstance.methods.confirmations(this.state.transactionId, this.state.account).call().then((accountConfirmed) => {
this.setState({accountConfirmed: accountConfirmed});
})
}
}
async handleClick(e){
e.preventDefault();
const {transactionId, value} = this.state;
this.setState({output: null, error: null, receipt: null});
try {
const toSend = accountConfirmed ? mswInstance.methods.revokeConfirmation(transactionId) : mswInstance.methods.confirmTransaction(transactionId);
const estimatedGas = await toSend.estimateGas({from: web3.eth.defaultAccount});
const receipt = await toSend.send({
from: web3.eth.defaultAccount,
gasLimit: estimatedGas
});
console.log(receipt);
this.setState({receipt});
} catch(err) {
console.error(err);
this.setState({error: err.message});
}
}
render(){
const {accountConfirmed, confirmations, required} = this.state;
const txt = " ["+confirmations+"/"+required+"]";
return(
<form>
<Button disabled={!this.state.control} type="submit" bsStyle="primary" onClick={(e) => this.handleClick(e)}>{ (accountConfirmed ? "Revoke" : "Confirm")+txt }</Button>
</form>)
}
}
export default MSWConfirmation;

View File

@ -0,0 +1,100 @@
import React, { Fragment } from 'react';
import { FormGroup, ControlLabel, FormControl, Button, Alert, } from 'react-bootstrap';
class MSWAddOwner extends React.Component {
constructor(props){
super(props);
this.state = {
account: props.account,
mswInstance: props.instance,
input: {
owner: ''
},
error: null,
mined: null
};
}
handleChangeFile(e) {
const {input} = this.state;
input.file = [e.target];
this.setState({input});
}
handleChange(e, name) {
const {input} = this.state;
input[name] = e.target.value;
this.setState({input});
}
handleCheckbox(e, name) {
const {input} = this.state;
input[name] = e.target.checked;
this.setState({input});
}
async handleClick(e){
e.preventDefault();
const {input, value} = this.state;
this.setState({output: null, error: null, receipt: null});
try {
const toSend = this.state.mswInstance.methods.addOwner(input.owner);
const MsSend = this.state.mswInstance.submitTransaction(
this.state.mswInstance.address, 0, toSend.encodeABI
)
const estimatedGas = await MsSend.estimateGas({from: this.state.account});
const receipt = await MsSend.send({
from: this.state.account,
gasLimit: estimatedGas
});
console.log(receipt);
this.setState({receipt});
} catch(err) {
console.error(err);
this.setState({error: err.message});
}
}
render(){
const {input, value, error, output, receipt} = this.state;
return <div className="formSection">
<h3>addOwner</h3>
<form>
<FormGroup>
<ControlLabel>owner</ControlLabel>
<FormControl
type="text"
defaultValue={ input.owner }
placeholder="address"
onChange={(e) => this.handleChange(e, 'owner')}
/>
</FormGroup>
{ error != null && <Alert bsStyle="danger">{error}</Alert> }
<Button type="submit" bsStyle="primary" onClick={(e) => this.handleClick(e)}>Send</Button>
{
receipt &&
<Fragment>
<Alert bsStyle={isSuccess(receipt.status) ? 'success' : 'danger'}>{isSuccess(receipt.status) ? 'Success' : 'Failure / Revert'} - Transaction Hash: {receipt.transactionHash}</Alert>
</Fragment>
}
</form>
</div>;
}
}
export default MSWAddOwner;

View File

@ -0,0 +1,86 @@
import React from 'react';
import { Table, Button } from 'react-bootstrap';
import MSWAddOwner from './owner-add';
class MSWOwnerTable extends React.Component {
constructor(props) {
super(props);
this.state = {
control: props.control,
account: props.account,
mswInstance: props.instance,
owners: []
}
this.load()
}
load() {
this.state.mswInstance.methods.getOwners().call().then((owners) => {
this.setState({owners:owners});
})
}
async removeOwner(e, account){
e.preventDefault();
this.setState({output: null, error: null, receipt: null});
try {
const toSend = this.state.mswInstance.methods.removeOwner(account);
const MsSend = this.state.mswInstance.submitTransaction(
this.state.mswInstance.address, 0, toSend.encodeABI
)
const estimatedGas = await MsSend.estimateGas({from: this.state.account});
const receipt = await MsSend.send({
from: this.state.account,
gasLimit: estimatedGas
});
console.log(receipt);
this.setState({receipt});
} catch(err) {
console.error(err);
this.setState({error: err.message});
}
}
render() {
const owners = this.state.owners.map((address, index) => (
<tr key={index}>
<td>{address}</td>
<td><Button disabled={!this.state.control} type="submit" bsStyle="primary" onClick={(e) => this.removeOwner(e, address)}>Remove</Button></td>
</tr>)
)
return (
<React.Fragment>
{this.state.control && <MSWAddOwner instance={this.state.mswInstance} account={this.state.account} />}
<div>
<Table size="sm" responsive={true} striped bordered hover >
<thead>
<tr>
<th>Owner</th>
<th></th>
</tr>
</thead>
<tbody>
{owners}
</tbody>
</Table>
</div>
</React.Fragment>
)
}
}
export default MSWOwnerTable;

View File

@ -0,0 +1,115 @@
import React from 'react';
import { FormGroup, ControlLabel, FormControl, Button, Alert } from 'react-bootstrap';
class MSWSubmitTransaction extends React.Component {
constructor(props){
super(props);
this.state = {
account: props.account,
mswInstance: props.instance,
input: {
destination: '',
value: '',
data: ''
},
error: null,
mined: null
};
}
handleChangeFile(e) {
const {input} = this.state;
input.file = [e.target];
this.setState({input});
}
handleChange(e, name) {
const {input} = this.state;
input[name] = e.target.value;
this.setState({input});
}
handleCheckbox(e, name) {
const {input} = this.state;
input[name] = e.target.checked;
this.setState({input});
}
async handleClick(e){
e.preventDefault();
const {input, value} = this.state;
this.setState({output: null, error: null, receipt: null});
try {
const toSend = this.state.mswInstance.methods.submitTransaction(input.destination, input.value, input.data);
const estimatedGas = await toSend.estimateGas({from: this.state.account});
const receipt = await toSend.send({
from: this.state.account,
gasLimit: estimatedGas
});
console.log(receipt);
this.setState({receipt});
} catch(err) {
console.error(err);
this.setState({error: err.message});
}
}
render(){
const {input, value, error, output, receipt} = this.state;
return <div className="formSection">
<h3>submitTransaction</h3>
<form>
<FormGroup>
<ControlLabel>destination</ControlLabel>
<FormControl
type="text"
defaultValue={ input.destination }
placeholder="address"
onChange={(e) => this.handleChange(e, 'destination')}
/>
</FormGroup>
<FormGroup>
<ControlLabel>value</ControlLabel>
<FormControl
type="text"
defaultValue={ input.value }
placeholder="uint256"
onChange={(e) => this.handleChange(e, 'value')}
/>
</FormGroup>
<FormGroup>
<ControlLabel>data</ControlLabel>
<FormControl
type="text"
defaultValue={ input.data }
placeholder="bytes"
onChange={(e) => this.handleChange(e, 'data')}
/>
</FormGroup>
{ error != null && <Alert bsStyle="danger">{error}</Alert> }
<Button type="submit" bsStyle="primary" onClick={(e) => this.handleClick(e)}>Send</Button>
{
receipt &&
<Fragment>
<Alert bsStyle={isSuccess(receipt.status) ? 'success' : 'danger'}>{isSuccess(receipt.status) ? 'Success' : 'Failure / Revert'} - Transaction Hash: {receipt.transactionHash}</Alert>
</Fragment>
}
</form>
</div>;
}
}
export default MSWSubmitTransaction;

View File

@ -0,0 +1,101 @@
import React from 'react';
import { Table } from 'react-bootstrap';
import MSWConfirmation from './confirmation';
import MSWSubmitTransaction from './transaction-submit';
class MSWTransactionTable extends React.Component {
constructor(props) {
super(props);
this.state = {
control: props.control,
account: props.account,
mswInstance: props.instance,
transactionCount: 0,
executedTxs: [],
pendingTxs: []
}
this.loadAll()
}
loadAll() {
this.state.mswInstance.methods.transactionCount().call().then((count) => {
this.transactionCount = count;
if(count > 0){
for(var i=0; i < count; i++){
this.loadTx(i);
}
}
})
}
loadTx(txId) {
this.state.mswInstance.methods.transactions(txId).call().then((val) => {
val.id = txId;
if(val.executed){
this.state.executedTxs.push(val);
} else {
this.state.pendingTxs.push(val);
}
this.forceUpdate();
})
}
render() {
const pendingTxs = this.state.pendingTxs.map((tx, index) => (
<tr key={index}>
<td>{tx.id}</td>
<td><MSWConfirmation instance={this.state.mswInstance} control={this.state.control} account={this.state.account} id={tx.id} /></td>
<td>To: {tx.destination} <br/>
Value: {tx.value} <br/>
Data: {tx.data}</td>
</tr>)
)
const executedTxs = this.state.executedTxs.map((tx, index) => (
<tr key={index}>
<td>{tx.id}</td>
<td>To: {tx.destination} <br/>
Value: {tx.value} <br/>
Data: {tx.data}</td>
</tr>)
)
return (
<React.Fragment>
{this.state.control && <p>Warning: You are legally responsable by what you approve.</p>}
{this.state.control && <p>Only approve when you are sure the execution is desired.</p>}
{this.state.control && <MSWSubmitTransaction instance={this.state.mswInstance} account={this.state.account} /> }
<div>
<Table size="sm" responsive={true} striped bordered hover >
<thead>
<tr>
<th>TxId</th>
<th>Confirmations</th>
<th>Tx Info</th>
</tr>
</thead>
<tbody>
{pendingTxs}
</tbody>
</Table>
<Table size="sm" responsive={true} striped bordered hover >
<thead>
<tr>
<th>TxId</th>
<th>Tx Info</th>
</tr>
</thead>
<tbody>
{executedTxs}
</tbody>
</Table>
</div>
</React.Fragment>
)
}
}
export default MSWTransactionTable;

98
app/dapp.js Normal file
View File

@ -0,0 +1,98 @@
import React from 'react';
import ReactDOM from 'react-dom';
import EmbarkJS from 'Embark/EmbarkJS';
import MultiSigWallet from 'Embark/contracts/MultiSigWallet';
import MultiSigWalletUI from './components/multisigwallet';
import { Alert, FormGroup, ControlLabel, FormControl } from 'react-bootstrap';
import './dapp.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
activeKey: 1,
blockchainEnabled: false ,
mswInstance: null,
account: null,
isOwner: false,
contractSetError: null,
contractAddress: null
};
}
componentDidMount() {
EmbarkJS.onReady((err) => {
this.setState({blockchainEnabled: true});
if (err) {
// If err is not null then it means something went wrong connecting to ethereum
// you can use this to ask the user to enable metamask for e.g
return this.setState({error: err.message || err});
}
});
}
setContractAddress(e) {
this.setState({contractSetError: null})
try{
const contractAddress = web3.utils.toChecksumAddress(e.target.value);
web3.eth.getCode(contractAddress).then((code) => {
if(code.length > 2){
let mswInstance = new web3.eth.Contract(MultiSigWallet._jsonInterface, contractAddress)
mswInstance.methods.required().call().then((req) => {
if(req > 0){
EmbarkJS.enableEthereum().then((s)=>{
if(s){
let defaultAccount = web3.utils.toChecksumAddress(s[0]);
mswInstance.methods.isOwner(defaultAccount).call().then((isOwner) => {
this.setState({isOwner:isOwner, account: defaultAccount, contractAddress: contractAddress, mswInstance: mswInstance});
})
}
})
} else {
this.setState({contractSetError: "Invalid MultiSigWallet"})
}
}).catch((e) => {
this.setState({contractSetError: "Not a MultiSigWallet"})
})
} else {
this.setState({contractSetError: "Not a smart contract"})
}
})
}catch(e){
this.setState({contractSetError: e.toString()})
}
}
render() {
if (this.state.error) {
return (<div>
<div>Something went wrong connecting to ethereum. Please make sure you have a node running or are using metamask to connect to the ethereum network:</div>
<div>{this.state.error}</div>
</div>);
}
return (
<div>
<h2>MultiSig Wallet</h2>
{!this.state.mswInstance && <form>
<FormGroup>
<ControlLabel>Contract Address:</ControlLabel>
<FormControl
type="text"
defaultValue={ this.state.contractAddress }
placeholder="address"
onChange={(e) => this.setContractAddress(e)}
/>
</FormGroup>
{ this.state.contractSetError != null && <Alert bsStyle="danger">{this.state.contractSetError}</Alert> }
</form>}
{ this.state.mswInstance && <MultiSigWalletUI isOwner={this.state.isOwner} account={this.state.account} instance={this.state.mswInstance} /> }
</div>);
}
}
ReactDOM.render(<App></App>, document.getElementById('app'));

View File

@ -1,7 +1,7 @@
<!doctype html>
<html lang="en" dir="ltr">
<head>
<title>MultiSigWalletWithDailyLimit</title>
<title>MultiSigWallet</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style type="text/css">
.formSection{
@ -11,6 +11,6 @@
</head>
<body class="container">
<div id="app"></div>
<script src="js/multisig.js"></script>
<script src="js/dapp.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -13,26 +13,12 @@ module.exports = {
gas: "auto",
strategy: "explicit",
contracts: {
MultiSigWalletWithDailyLimit: {
deploy: true,
args: [["$accounts[0]"], 1, 0]
}
}
},
livenet: {
contracts: {
MultiSigWalletWithDailyLimit: {
deploy: false,
address: "0xB913626032140A86c77D1fdde4f611A00D589C55"
}
}
},
testnet: {
contracts: {
}
},
rinkeby: {
contracts: {
}
}
}

View File

@ -1,106 +0,0 @@
pragma solidity >=0.5.0 <0.6.0;
import "./MultiSigWallet.sol";
/// @title Multisignature wallet with daily limit - Allows an owner to withdraw a daily limit without multisig.
/// @author Stefan George - <stefan.george@consensys.net>
contract MultiSigWalletWithDailyLimit is MultiSigWallet {
/*
* Events
*/
event DailyLimitChange(uint dailyLimit);
/*
* Storage
*/
uint public dailyLimit;
uint public lastDay;
uint public spentToday;
/*
* Public functions
*/
/// @dev Contract constructor sets initial owners, required number of confirmations and daily withdraw limit.
/// @param _owners List of initial owners.
/// @param _required Number of required confirmations.
/// @param _dailyLimit Amount in wei, which can be withdrawn without confirmations on a daily basis.
constructor(address[] memory _owners, uint _required, uint _dailyLimit)
public
MultiSigWallet(_owners, _required)
{
dailyLimit = _dailyLimit;
}
/// @dev Allows to change the daily limit. Transaction has to be sent by wallet.
/// @param _dailyLimit Amount in wei.
function changeDailyLimit(uint _dailyLimit)
public
onlyWallet
{
dailyLimit = _dailyLimit;
emit DailyLimitChange(_dailyLimit);
}
/// @dev Allows anyone to execute a confirmed transaction or ether withdraws until daily limit is reached.
/// @param transactionId Transaction ID.
function executeTransaction(uint transactionId)
public
ownerExists(msg.sender)
confirmed(transactionId, msg.sender)
notExecuted(transactionId)
{
Transaction storage txn = transactions[transactionId];
bool _confirmed = isConfirmed(transactionId);
if (_confirmed || txn.data.length == 0 && isUnderLimit(txn.value)) {
txn.executed = true;
if (!_confirmed)
spentToday += txn.value;
(bool success, ) = txn.destination.call.value(txn.value)(txn.data);
if (success)
emit Execution(transactionId);
else {
emit ExecutionFailure(transactionId);
txn.executed = false;
if (!_confirmed)
spentToday -= txn.value;
}
}
}
/*
* Internal functions
*/
/// @dev Returns if amount is within daily limit and resets spentToday after one day.
/// @param amount Amount to withdraw.
/// @return Returns if amount is under daily limit.
function isUnderLimit(uint amount)
internal
returns (bool)
{
if (now > lastDay + 24 hours) {
lastDay = now;
spentToday = 0;
}
if (spentToday + amount > dailyLimit || spentToday + amount < spentToday)
return false;
return true;
}
/*
* Web3 call functions
*/
/// @dev Returns maximum withdraw amount.
/// @return Returns amount.
function calcMaxWithdraw()
public
view
returns (uint)
{
if (now > lastDay + 24 hours)
return dailyLimit;
if (dailyLimit < spentToday)
return 0;
return dailyLimit - spentToday;
}
}

View File

@ -1,7 +1,7 @@
{
"contracts": ["contracts/**"],
"app": {
"js/multisig.js": ["app/multisig.js"],
"js/dapp.js": ["app/dapp.js"],
"index.html": "app/index.html"
},
"buildDir": "dist/",