diff --git a/app/components/multisigwallet.js b/app/components/multisigwallet.js
new file mode 100644
index 0000000..3e5d294
--- /dev/null
+++ b/app/components/multisigwallet.js
@@ -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 (
+
+ {this.state.mswInstance._address}
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+
+export default MultiSigWalletUI;
diff --git a/app/components/multisigwallet/confirmation.js b/app/components/multisigwallet/confirmation.js
new file mode 100644
index 0000000..d6b71c2
--- /dev/null
+++ b/app/components/multisigwallet/confirmation.js
@@ -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(
+
)
+ }
+}
+
+export default MSWConfirmation;
\ No newline at end of file
diff --git a/app/components/multisigwallet/owner-add.js b/app/components/multisigwallet/owner-add.js
new file mode 100644
index 0000000..c06617a
--- /dev/null
+++ b/app/components/multisigwallet/owner-add.js
@@ -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 ;
+ }
+}
+
+export default MSWAddOwner;
\ No newline at end of file
diff --git a/app/components/multisigwallet/owner-table.js b/app/components/multisigwallet/owner-table.js
new file mode 100644
index 0000000..1d6cca9
--- /dev/null
+++ b/app/components/multisigwallet/owner-table.js
@@ -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) => (
+
+ {address} |
+ |
+
)
+ )
+
+ return (
+
+ {this.state.control && }
+
+
+
+
+ Owner |
+ |
+
+
+
+ {owners}
+
+
+
+
+
+ )
+
+ }
+}
+
+export default MSWOwnerTable;
\ No newline at end of file
diff --git a/app/components/multisigwallet/transaction-submit.js b/app/components/multisigwallet/transaction-submit.js
new file mode 100644
index 0000000..0165895
--- /dev/null
+++ b/app/components/multisigwallet/transaction-submit.js
@@ -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 ;
+ }
+}
+
+export default MSWSubmitTransaction;
\ No newline at end of file
diff --git a/app/components/multisigwallet/transaction-table.js b/app/components/multisigwallet/transaction-table.js
new file mode 100644
index 0000000..fca1802
--- /dev/null
+++ b/app/components/multisigwallet/transaction-table.js
@@ -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) => (
+
+ {tx.id} |
+ |
+ To: {tx.destination}
+ Value: {tx.value}
+ Data: {tx.data} |
+
)
+ )
+ const executedTxs = this.state.executedTxs.map((tx, index) => (
+
+ {tx.id} |
+ To: {tx.destination}
+ Value: {tx.value}
+ Data: {tx.data} |
+
)
+ )
+ return (
+
+ {this.state.control && Warning: You are legally responsable by what you approve.
}
+ {this.state.control && Only approve when you are sure the execution is desired.
}
+ {this.state.control && }
+
+
+
+
+ TxId |
+ Confirmations |
+ Tx Info |
+
+
+
+ {pendingTxs}
+
+
+
+
+
+ TxId |
+ Tx Info |
+
+
+
+ {executedTxs}
+
+
+
+
+ )
+
+ }
+}
+
+export default MSWTransactionTable;
\ No newline at end of file
diff --git a/app/dapp.js b/app/dapp.js
new file mode 100644
index 0000000..ce8b7bd
--- /dev/null
+++ b/app/dapp.js
@@ -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 (
+
Something went wrong connecting to ethereum. Please make sure you have a node running or are using metamask to connect to the ethereum network:
+
{this.state.error}
+
);
+ }
+ return (
+
+
MultiSig Wallet
+
+ {!this.state.mswInstance &&
}
+ { this.state.mswInstance &&
}
+
);
+ }
+}
+
+ReactDOM.render(, document.getElementById('app'));
diff --git a/app/index.html b/app/index.html
index 594df14..b2625c5 100644
--- a/app/index.html
+++ b/app/index.html
@@ -1,7 +1,7 @@
- MultiSigWalletWithDailyLimit
+ MultiSigWallet