diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..ebe25fbba --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,19 @@ +# Javascript Node CircleCI 2.0 configuration file +version: 2 +jobs: + build: + docker: + # specify the version you desire here + - image: circleci/node:7.10 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/mongo:3.4.4 + + steps: + - checkout + - run: curl https://install.meteor.com/ | sh + - run: cd app && npm install && npm run build + + diff --git a/.travis.yml b/.travis.yml index 82db32f5e..3b294ca2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,10 @@ node_js: "7" sudo: required install: - - npm install -g meteor-build-client + - cd app + - npm install - curl https://install.meteor.com/ | sh script: - - cd app - - echo `pwd` - - meteor-build-client ../build --path "" + - npm run build diff --git a/README.md b/README.md index 93fb79811..2e3b8eab1 100644 --- a/README.md +++ b/README.md @@ -4,49 +4,56 @@ The Ethereum wallet. [![Build Status](https://travis-ci.org/ethereum/meteor-dapp-wallet.svg?branch=master)](https://travis-ci.org/ethereum/meteor-dapp-wallet) -**NOTE** The wallet is not yet official released, -can contain severe bugs! +**PLEASE NOTE:** This wallet is not yet officially released, +and can contain severe bugs! Please use at your own risk. + +## Install + +If you don't have [Meteor](https://www.meteor.com/install): + + $ curl https://install.meteor.com/ | sh + +Install npm dependencies: + + $ cd meteor-dapp-wallet/app + $ npm install ## Development -Start an `geth` node and the app using meteor and open http://localhost:3000 in your browser: +Start a `geth` node: $ geth --ws --wsorigins "http://localhost:3000" --unlock -Starting the wallet dapp using [Meteor](https://meteor.com/install) +Run dev server: $ cd meteor-dapp-wallet/app $ meteor -Go to http://localhost:3000 +Navigate to http://localhost:3000 ## Deployment -To create a build version of your app run: +To create a build: -// install meteor-build-client -$ npm install -g meteor-build-client - - // bundle dapp + $ npm install -g meteor-build-client $ cd meteor-dapp-wallet/app + $ npm install $ meteor-build-client ../build --path "" -This will generate the files in the `../build` folder. Double click the index.html to start the app. -To make routing work properly you need to build it using: +This will generate the files in the `../build` folder. - $ meteor-build-client ../build +Navigating to `index.html` will start the app, but you will need to serve it over a local server like [MAMP](https://www.mamp.info). -And start a local server which points with its document root into the `../build` folder, -so that you can open the app using `http://localhost:80/` +--- -To deploy them to the **wallet.ethereum.org** site, execute these commands (from the app folder): +To deploy to the **wallet.ethereum.org** site, execute these commands: - git checkout gh-pages - git merge develop - cd app - meteor-build-client ../build --path "/" + $ git checkout gh-pages + $ git merge develop + $ cd app + $ meteor-build-client ../build --path "/" -And push (or PR) your changes to the gh-pages branch. +And push (or PR) your changes to the `gh-pages` branch. --- diff --git a/app/.meteor/versions b/app/.meteor/versions index 976dca786..63885220e 100644 --- a/app/.meteor/versions +++ b/app/.meteor/versions @@ -37,7 +37,7 @@ es5-shim@4.7.3 ethereum:accounts@1.1.0 ethereum:blocks@1.1.0 ethereum:dapp-styles@0.5.8 -ethereum:elements@1.2.0 +ethereum:elements@1.2.1 ethereum:tools@1.1.0 ethereum:web3@1.0.0-beta.33 fastclick@1.0.13 diff --git a/app/client/lib/ethereum/observeBlocks.js b/app/client/lib/ethereum/observeBlocks.js index 64cfa7d54..1a8832179 100644 --- a/app/client/lib/ethereum/observeBlocks.js +++ b/app/client/lib/ethereum/observeBlocks.js @@ -162,6 +162,9 @@ observeLatestBlocks = function() { updateBalances(); // GET the latest blockchain information + // setInterval(()=>{ + // updateBalances(); + // }, 1000 * 15); web3.eth.subscribe('newBlockHeaders', function(e, res) { if (!e) { updateBalances(); diff --git a/app/client/lib/ethereum/observeTransactions.js b/app/client/lib/ethereum/observeTransactions.js index 3eeabcb03..9732fe964 100644 --- a/app/client/lib/ethereum/observeTransactions.js +++ b/app/client/lib/ethereum/observeTransactions.js @@ -10,17 +10,18 @@ addTransactionAfterSend = function( to, gasPrice, estimatedGas, - data, + rawData, tokenId ) { var jsonInterface = undefined, contractName = undefined, + data = undefined, txId = Helpers.makeId('tx', txHash); if (_.isObject(data)) { contractName = data.contract.name.replace(/([A-Z])/g, ' $1'); jsonInterface = data.contract.jsonInterface; - data = data.data; + data = rawData.data; } Transactions.upsert(txId, { diff --git a/app/client/lib/ethereum/walletInterface.js b/app/client/lib/ethereum/walletInterface.js index bbf7afc6b..640be9799 100644 --- a/app/client/lib/ethereum/walletInterface.js +++ b/app/client/lib/ethereum/walletInterface.js @@ -452,91 +452,84 @@ checkWalletOwners = function(address) { info: '' }; - if (web3.utils.isAddress(address)) { - address = address.toLowerCase(); - WalletContract.options.address = address; - var myContract = WalletContract; - - myContract.m_numOwners(function(e, numberOfOwners) { - if (!e) { - numberOfOwners = numberOfOwners.toNumber(); - - if (numberOfOwners > 0) { - var owners = []; - - // go through the number of owners we stop - P.all( - _.map(_.range(100), function(i) { - return new P(function(resolve, reject) { - web3.eth.getStorageAt(address, 2 + i, function( - e, - ownerAddress + if (!web3.utils.isAddress(address)) return; + address = address.toLowerCase(); + WalletContract.options.address = address; + var myContract = WalletContract; + + myContract.methods.m_numOwners().call(function(e, numberOfOwners) { + if (e) reject(e); + + numberOfOwners = Number(numberOfOwners); + if (numberOfOwners > 0) { + var owners = []; + + // go through the number of owners we stop + P.all( + _.map(_.range(numberOfOwners), function(i) { + return new P(function(resolve, reject) { + web3.eth.getStorageAt(address, 2 + i, function(e, ownerAddress) { + if (!e) { + ownerAddress = ownerAddress.replace( + '0x000000000000000000000000', + '0x' + ); + + ownerAddress = web3.utils.toChecksumAddress(ownerAddress); + + if (owners.length > numberOfOwners) return resolve(); + + if ( + web3.utils.isAddress(ownerAddress) && + ownerAddress !== + '0x0000000000000000000000000000000000000000' ) { - if (!e) { - ownerAddress = ownerAddress.replace( - '0x000000000000000000000000', - '0x' - ); - - if (owners.length > numberOfOwners) return resolve(); - - if ( - web3.utils.isAddress(ownerAddress) && - ownerAddress !== - '0x0000000000000000000000000000000000000000' - ) { - myContract.isOwner.call( - ownerAddress, - { from: ownerAddress }, - function(e, isOwner) { - if (!e && isOwner) { - owners.push(ownerAddress); - owners = _.uniq(owners); - owners.sort(); - } - - resolve(); - } - ); - } else { + myContract.methods + .isOwner(ownerAddress) + .call({ from: ownerAddress }, function(e, isOwner) { + if (!e && isOwner) { + owners.push(ownerAddress); + owners = _.uniq(owners); + owners.sort(); + } + resolve(); - } - } - }); - }); - }) - ).then( - function() { - returnValue.owners = owners; - - if ((account = Helpers.getAccountByAddress({ $in: owners }))) { - returnValue.info = TAPi18n.__( - 'wallet.newWallet.accountType.import.youreOwner', - { account: account.name } - ); - } else { - returnValue.info = TAPi18n.__( - 'wallet.newWallet.accountType.import.watchOnly' - ); + }); + } else { + resolve(); + } } + }); + }); + }) + ).then( + function() { + returnValue.owners = owners; + + if ((account = Helpers.getAccountByAddress({ $in: owners }))) { + returnValue.info = TAPi18n.__( + 'wallet.newWallet.accountType.import.youreOwner', + { account: account.name } + ); + } else { + returnValue.info = TAPi18n.__( + 'wallet.newWallet.accountType.import.watchOnly' + ); + } - resolve(returnValue); - return null; - }, - function() { - reject(); - } - ); - } else { - returnValue.info = TAPi18n.__( - 'wallet.newWallet.accountType.import.notWallet' - ); resolve(returnValue); + return null; + }, + function() { + reject(); } - } else { - reject(e); - } - }); - } + ); + } else { + returnValue.info = TAPi18n.__( + 'wallet.newWallet.accountType.import.notWallet' + ); + resolve(returnValue); + } + }); }); }; diff --git a/app/client/lib/helpers/helperFunctions.js b/app/client/lib/helpers/helperFunctions.js index 60a1d5a98..f483f6225 100644 --- a/app/client/lib/helpers/helperFunctions.js +++ b/app/client/lib/helpers/helperFunctions.js @@ -18,8 +18,7 @@ Get the default contract example @method getDefaultContractExample **/ Helpers.getDefaultContractExample = function(withoutPragma) { - var source = - 'contract MyContract {\n /* Constructor */\n function MyContract() public {\n\n }\n}'; + var source = 'contract MyContract {\n constructor() public {\n\n }\n}'; if (withoutPragma) { return source; @@ -91,7 +90,7 @@ Helpers.getLocalStorageSize = function() { var size = 0; if (localStorage) { _.each(Object.keys(localStorage), function(key) { - size += localStorage[key].length * 2 / 1024 / 1024; + size += (localStorage[key].length * 2) / 1024 / 1024; }); } @@ -192,6 +191,10 @@ Helpers.showNotification = function(i18nText, values, callback) { if (typeof mist !== 'undefined') mist.sounds.bip(); }; +var multipleCaseAddresses = function(address) { + return [address.toLowerCase(), web3.utils.toChecksumAddress(address)]; +}; + /** Gets the docuement matching the given addess from the EthAccounts or Wallets collection. @@ -200,13 +203,28 @@ Gets the docuement matching the given addess from the EthAccounts or Wallets col @param {Boolean} reactive */ Helpers.getAccountByAddress = function(address, reactive) { + if (address == null) { + return null; + } var options = reactive === false ? { reactive: false } : {}; - // if(_.isString(address)) - // address = address.toLowerCase(); + var query; + + if (_.isString(address)) { + query = { address: { $in: multipleCaseAddresses(address) } }; + } else if ('$in' in address) { + // If provided query is a list of accounts, unwrap it and adds redundant addresses. + var addressArray = _.flatten( + address.$in.map(e => multipleCaseAddresses(e)) + ); + query = { address: { $in: addressArray } }; + } else { + query = { address: address }; + } + return ( - EthAccounts.findOne({ address: address }, options) || - Wallets.findOne({ address: address }, options) || - CustomContracts.findOne({ address: address }, options) + EthAccounts.findOne(query, options) || + Wallets.findOne(query, options) || + CustomContracts.findOne(query, options) ); }; @@ -219,7 +237,10 @@ Gets the docuement matching the given query from the EthAccounts or Wallets coll */ Helpers.getAccounts = function(query, reactive) { var options = reactive === false ? { reactive: false } : {}; - if (_.isString(query.address)) query.address = query.address.toLowerCase(); + if (_.isString(query.address)) { + query.address = { $in: multipleCaseAddresses(query.address) }; + } + return EthAccounts.find(query, options) .fetch() .concat(Wallets.find(query, options).fetch()); diff --git a/app/client/lib/helpers/templateHelpers.js b/app/client/lib/helpers/templateHelpers.js index ea157587e..65d80eca9 100644 --- a/app/client/lib/helpers/templateHelpers.js +++ b/app/client/lib/helpers/templateHelpers.js @@ -26,7 +26,7 @@ Check if in mist @method (isMist) **/ Template.registerHelper('isMist', function() { - return typeof window.mist !== 'undefined'; + return window.mistMode === undefined && window.mist !== undefined; }); /** @@ -35,7 +35,17 @@ Check if in mist and in mist mode @method (isWalletMode) **/ Template.registerHelper('isWalletMode', function() { - return window.mistMode === 'wallet' || typeof mist === 'undefined'; // also show network info in normal browsers + // also show network info in normal browsers + return window.mistMode === 'wallet' || window.mist === undefined; +}); + +/** +Check if wallet was loaded from browser other than Mist + +@method (isBrowserMode) +**/ +Template.registerHelper('isBrowserMode', function() { + return window.mist === undefined; }); /** @@ -127,18 +137,27 @@ Template.registerHelper('selectAccounts', function(hideWallets) { { sort: { balance: 1 } } ).fetch(); + // array of objects + accounts = accounts.map(function(e) { + e.address = e.address.toLowerCase(); + return e; + }); + + // array of string addresses + // we can't be sure how the addresses would look like, checksum or lowercase, + // so we add both types to the search. + var accountsAddresses = _.flatten( + accounts.map(function(e) { + return [e.address, web3.utils.toChecksumAddress(e.address)]; + }) + ); + if (hideWallets !== true) accounts = _.union( Wallets.find( { - owners: { - $in: _.map(EthAccounts.find().fetch(), function(account) { - return account.address.toLowerCase(); - }) - }, - address: { - $exists: true - } + owners: { $in: accountsAddresses }, + address: { $exists: true } }, { sort: { name: 1 } @@ -255,5 +274,5 @@ Check if on main network @method (isMainNetwork) **/ Template.registerHelper('isMainNetwork', function() { - return Session.get('network') === 'main'; -}); \ No newline at end of file + return Session.get('network') === 'main'; +}); diff --git a/app/client/templates/elements/account.js b/app/client/templates/elements/account.js index 1235cde89..b3a434716 100644 --- a/app/client/templates/elements/account.js +++ b/app/client/templates/elements/account.js @@ -17,7 +17,7 @@ Block required until a transaction is confirmed. @property blocksForConfirmation @type Number */ -var blocksForConfirmation = 12; +var blocksForConfirmation = 3; Template['elements_account'].rendered = function() { // initiate the geo pattern @@ -127,7 +127,7 @@ Template['elements_account'].helpers({ return blocksForConfirmation >= confirmations && confirmations >= 0 ? { confirmations: confirmations, - percent: confirmations / blocksForConfirmation * 100 + percent: (confirmations / blocksForConfirmation) * 100 } : false; }, diff --git a/app/client/templates/elements/mistAlert.html b/app/client/templates/elements/mistAlert.html deleted file mode 100644 index ad127041d..000000000 --- a/app/client/templates/elements/mistAlert.html +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/app/client/templates/elements/mistAlert.js b/app/client/templates/elements/mistAlert.js deleted file mode 100644 index 9d1965224..000000000 --- a/app/client/templates/elements/mistAlert.js +++ /dev/null @@ -1,25 +0,0 @@ -var alertKey = 'alert_20171104-hidden'; -Template['mist_alert'].onRendered(function() { - TemplateVar.set('hidden', localStorage.getItem(alertKey)); -}); - -Template['mist_alert'].helpers({ - alertViewState: function() { - return !!TemplateVar.get('hidden') ? 'is-hidden' : ''; - }, - bubbleViewState: function() { - return !TemplateVar.get('hidden') ? 'is-hidden' : ''; - } -}); - -Template['mist_alert'].events({ - 'click .hide-alert': function() { - localStorage.setItem(alertKey, true); - TemplateVar.set('hidden', localStorage.getItem(alertKey)); - }, - - 'click .show-alert button': function() { - localStorage.setItem(alertKey, ''); - TemplateVar.set('hidden', localStorage.getItem(alertKey)); - } -}); diff --git a/app/client/templates/elements/newAccountButton.html b/app/client/templates/elements/newAccountButton.html new file mode 100644 index 000000000..dde908f9c --- /dev/null +++ b/app/client/templates/elements/newAccountButton.html @@ -0,0 +1,8 @@ + diff --git a/app/client/templates/elements/transactionTable.js b/app/client/templates/elements/transactionTable.js index 42bc8646f..541bbd69b 100644 --- a/app/client/templates/elements/transactionTable.js +++ b/app/client/templates/elements/transactionTable.js @@ -155,7 +155,9 @@ Template['elements_transactions_row'].helpers({ transactionType: function() { var to = Helpers.getAccountByAddress(this.to), from = Helpers.getAccountByAddress(this.from), - initiator = Helpers.getAccountByAddress(this.initiator), + initiator = this.initiator + ? Helpers.getAccountByAddress(this.initiator) + : null, sendData = this.data; if (from) @@ -226,7 +228,7 @@ Template['elements_transactions_row'].helpers({ return blocksForConfirmation >= confirmations && confirmations >= 0 ? { confirmations: confirmations, - percent: confirmations / blocksForConfirmation * 100 + percent: (confirmations / blocksForConfirmation) * 100 } : false; }, diff --git a/app/client/templates/views/account_create.js b/app/client/templates/views/account_create.js index 32333327c..e75a1f194 100644 --- a/app/client/templates/views/account_create.js +++ b/app/client/templates/views/account_create.js @@ -239,7 +239,7 @@ Template['views_account_create'].helpers({ Template['views_account_create'].events({ /** Check the owner of the imported wallet. - + @event change input.import, input input.import */ 'change input.import, input input.import': function(e, template) { @@ -254,7 +254,7 @@ Template['views_account_create'].events({ }, /** Check the owner that its not a contract wallet - + @event change input.owners, input input.owners */ 'change input.owners, input input.owners': function(e, template) { @@ -365,11 +365,6 @@ Template['views_account_create'].events({ var address = template.find('input.import').value; address = '0x' + address.replace('0x', '').toLowerCase(); - if (Wallets.findOne({ address: address })) - return GlobalNotification.warning({ - content: 'i18n:wallet.newWallet.error.alreadyExists', - duration: 2 - }); // reorganize owners, so that yourself is at place one var account = Helpers.getAccountByAddress({ $in: owners || [] }); @@ -378,17 +373,22 @@ Template['views_account_create'].events({ owners.unshift(account.address); } - Wallets.insert({ - owners: owners, - name: - template.find('input[name="accountName"]').value || - TAPi18n.__('wallet.accounts.defaultName'), - address: address, - balance: '0', - // TODO set to 0 - creationBlock: 300000, - imported: true - }); + Wallets.upsert( + { address: address }, + { + $set: { + owners: owners, + name: + template.find('input[name="accountName"]').value || + TAPi18n.__('wallet.accounts.defaultName'), + address: address, + balance: '0', + // TODO set to 0 + creationBlock: 300000 + // imported: true + } + } + ); FlowRouter.go('dashboard'); } diff --git a/app/client/templates/views/contracts.js b/app/client/templates/views/contracts.js index 0b271496c..1f1bfb172 100644 --- a/app/client/templates/views/contracts.js +++ b/app/client/templates/views/contracts.js @@ -141,8 +141,8 @@ var autoScanGetTokens = function(template) { TAPi18n.__('wallet.tokens.autoScan.status.downloadingList') ); - const tokenListURL = - 'https://raw.githubusercontent.com/MyEtherWallet/ethereum-lists/master/tokens/tokens-eth.json'; + var tokenListURL = + 'https://raw.githubusercontent.com/MyEtherWallet/ethereum-lists/master/dist/tokens/eth/tokens-eth.json'; const accounts = _.pluck( EthAccounts.find() @@ -184,6 +184,11 @@ var autoScanGetTokens = function(template) { Meteor.defer(function() { // defer to wait for autoScanStatus to update in UI first _.each(tokens, function(token) { + if (!web3.utils.isAddress(token.address)) { + console.log('Token address invalid: ', token.address); + return; + } + if (alreadySubscribed.includes(token.address)) { console.log('Already subscribed to ' + token.name); return; diff --git a/app/client/templates/views/dashboard.html b/app/client/templates/views/dashboard.html index 00bde8272..f26a2db42 100644 --- a/app/client/templates/views/dashboard.html +++ b/app/client/templates/views/dashboard.html @@ -1,7 +1,4 @@