simplify ui
This commit is contained in:
parent
e738876d2b
commit
1648936e2e
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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'));
|
|
@ -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>
|
2198
app/multisig.js
2198
app/multisig.js
File diff suppressed because it is too large
Load Diff
|
@ -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: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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/",
|
||||
|
|
Loading…
Reference in New Issue