From 236070668a51ede6249c2f5b15cad1d5ac9a422c Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Sun, 7 May 2017 16:36:47 +0300 Subject: [PATCH] add geth wallet back in --- lib/plugins/wallet/geth/geth.js | 151 ++++++++++++++++++++++++++++++++ package.json | 3 + yarn.lock | 94 +++++++++++++++++++- 3 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 lib/plugins/wallet/geth/geth.js diff --git a/lib/plugins/wallet/geth/geth.js b/lib/plugins/wallet/geth/geth.js new file mode 100644 index 00000000..84192d4e --- /dev/null +++ b/lib/plugins/wallet/geth/geth.js @@ -0,0 +1,151 @@ +const _ = require('lodash/fp') +const Web3 = require('web3') +const web3 = new Web3() +const hdkey = require('hdkey') +const Tx = require('ethereumjs-tx') +const pify = require('pify') + +const NAME = 'geth' + +const paymentPrefixPath = "m/44'/60'/0'/0'" +const defaultPrefixPath = "m/44'/60'/1'/0'" + +module.exports = { + NAME, + balance, + sendCoins, + newAddress, + getStatus, + sweep, + defaultAddress, + supportsHd: true +} + +if (!web3.isConnected()) { + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')) +} + +const hex = bigNum => '0x' + bigNum.truncated().toString(16) + +function sendCoins (account, toAddress, cryptoAtoms, cryptoCode) { + return generateTx(toAddress, defaultWallet(account), cryptoAtoms, false) + .then(pify(web3.eth.sendRawTransaction)) +} + +function validateCrypto (cryptoCode) { + if (cryptoCode === 'ETH') return Promise.resolve() + return Promise.reject(new Error('cryptoCode must be ETH')) +} + +function balance (account, cryptoCode) { + return validateCrypto(cryptoCode) + .then(() => pendingBalance(defaultAddress(account))) +} + +const pendingBalance = address => _balance(true, address) +const confirmedBalance = address => _balance(false, address) + +function _balance (includePending, address) { + const block = includePending ? 'pending' : undefined + + return pify(web3.eth.getBalance)(address.toLowerCase(), block) +} + +function generateTx (_toAddress, wallet, amount, includesFee) { + const fromAddress = '0x' + wallet.getAddress().toString('hex') + const toAddress = _toAddress.toLowerCase() + + const txTemplate = { + from: fromAddress, + to: toAddress, + value: amount + } + + const promises = [ + pify(web3.eth.estimateGas)(txTemplate), + pify(web3.eth.getGasPrice)(), + pify(web3.eth.getTransactionCount)(fromAddress) + ] + + return Promise.all(promises) + .then(arr => { + const gas = arr[0] + const gasPrice = arr[1] + const txCount = arr[2] + + const toSend = includesFee + ? amount.minus(gasPrice.times(gas)) + : amount + + const rawTx = { + nonce: txCount, + gasPrice: hex(gasPrice), + gasLimit: gas, + to: toAddress, + from: fromAddress, + value: hex(toSend) + } + + const tx = new Tx(rawTx) + const privateKey = wallet.getPrivateKey() + + tx.sign(privateKey) + + return '0x' + tx.serialize().toString('hex') + }) +} + +function defaultWallet (account) { + return defaultHdNode(account).deriveChild(0).getWallet() +} + +function defaultAddress (account) { + return defaultWallet(account).getChecksumAddressString() +} + +function sweep (account, cryptoCode, hdIndex) { + const wallet = paymentHdNode(account).deriveChild(hdIndex).getWallet() + const fromAddress = wallet.getChecksumAddressString() + + return confirmedBalance(fromAddress) + .then(r => { + if (r.eq(0)) return + + return generateTx(defaultAddress(account), wallet, r, true) + .then(signedTx => pify(web3.eth.sendRawTransaction)(signedTx)) + }) +} + +function newAddress (account, info) { + const childNode = paymentHdNode(account).deriveChild(info.hdIndex) + return Promise.resolve(childNode.getWallet().getChecksumAddressString()) +} + +function getStatus (account, toAddress, cryptoAtoms, cryptoCode) { + return validateCrypto(cryptoCode) + .then(() => confirmedBalance(toAddress)) + .then(confirmed => { + if (confirmed.gte(cryptoAtoms)) return {status: 'confirmed'} + + return pendingBalance(toAddress) + .then(pending => { + if (pending.gte(cryptoAtoms)) return {status: 'published'} + if (pending.gt(0)) return {status: 'insufficientFunds'} + return {status: 'notSeen'} + }) + }) +} + +function paymentHdNode (account) { + const masterSeed = account.seed + if (!masterSeed) throw new Error('No master seed!') + const key = hdkey.fromMasterSeed(masterSeed) + return key.derivePath(paymentPrefixPath) +} + +function defaultHdNode (account) { + const masterSeed = account.seed + if (!masterSeed) throw new Error('No master seed!') + const key = hdkey.fromMasterSeed(masterSeed) + return key.derivePath(defaultPrefixPath) +} diff --git a/package.json b/package.json index 2e4eeec7..5880149a 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,12 @@ "bitgo": "^2.2.4", "body-parser": "^1.15.1", "cookie-parser": "^1.4.3", + "ethereumjs-tx": "^1.3.0", "express": "^4.13.4", "express-limiter": "^1.6.0", "express-rate-limit": "^2.6.0", "got": "^6.6.3", + "hdkey": "^0.7.1", "helmet": "^3.1.0", "lodash": "^4.17.2", "mem": "^1.1.0", @@ -40,6 +42,7 @@ "socket.io-client": "^1.7.1", "twilio": "^2.11.1", "uuid": "^3.0.0", + "web3": "^0.18.4", "winston": "^2.3.0" }, "repository": { diff --git a/yarn.lock b/yarn.lock index 312cb1d0..3bb488c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -706,6 +706,10 @@ bignumber.js@^4.0.0, bignumber.js@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-4.0.1.tgz#1ffd30d349366e078bd6f7dc97907e6da9a71888" +"bignumber.js@git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2": + version "2.0.7" + resolved "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + binary-extensions@^1.0.0: version "1.8.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" @@ -1112,6 +1116,13 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" +coinstring@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/coinstring/-/coinstring-2.3.0.tgz#cdb63363a961502404a25afb82c2e26d5ff627a4" + dependencies: + bs58 "^2.0.1" + create-hash "^1.1.1" + color-convert@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" @@ -1302,6 +1313,10 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +crypto-js@^3.1.4: + version "3.1.8" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-3.1.8.tgz#715f070bf6014f2ae992a98b3929258b713f08d5" + crypto-random-string@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" @@ -1696,6 +1711,10 @@ etag@~1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" +ethereum-common@^0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" + ethereumjs-abi@^0.6.2: version "0.6.4" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.4.tgz#9ba1bb056492d00c27279f6eccd4d58275912c1a" @@ -1703,6 +1722,13 @@ ethereumjs-abi@^0.6.2: bn.js "^4.10.0" ethereumjs-util "^4.3.0" +ethereumjs-tx@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.0.tgz#09f9a3239ac6d9a268cad122967345062a536abd" + dependencies: + ethereum-common "^0.0.18" + ethereumjs-util "^5.0.0" + ethereumjs-util@^4.3.0, ethereumjs-util@^4.4.1: version "4.5.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6" @@ -1713,6 +1739,24 @@ ethereumjs-util@^4.3.0, ethereumjs-util@^4.4.1: rlp "^2.0.0" secp256k1 "^3.0.1" +ethereumjs-util@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.1.1.tgz#122fb38dea747dc62b3aebfc365d1bd48be4b73e" + dependencies: + bn.js "^4.8.0" + create-hash "^1.1.2" + ethjs-util "^0.1.3" + keccak "^1.0.2" + rlp "^2.0.0" + secp256k1 "^3.0.1" + +ethjs-util@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.4.tgz#1c8b6879257444ef4d3f3fbbac2ded12cd997d93" + dependencies: + is-hex-prefixed "1.0.0" + strip-hex-prefix "1.0.0" + eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" @@ -2209,6 +2253,13 @@ hawk@~3.1.3: hoek "2.x.x" sntp "1.x.x" +hdkey@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-0.7.1.tgz#caee4be81aa77921e909b8d228dd0f29acaee632" + dependencies: + coinstring "^2.0.0" + secp256k1 "^3.0.1" + helmet-csp@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/helmet-csp/-/helmet-csp-2.4.0.tgz#7e53a157167a0645aadd7177d12ae6c605c1842e" @@ -2379,7 +2430,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -2487,6 +2538,10 @@ is-glob@^2.0.0, is-glob@^2.0.1: dependencies: is-extglob "^1.0.0" +is-hex-prefixed@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" + is-my-json-valid@^2.12.4: version "2.16.0" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" @@ -2749,6 +2804,15 @@ jws@^3.0.0: jwa "^1.1.4" safe-buffer "^5.0.1" +keccak@^1.0.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/keccak/-/keccak-1.2.0.tgz#b53618fc7961b642f6e73f1546eec3329f7effe0" + dependencies: + bindings "^1.2.1" + inherits "^2.0.3" + nan "^2.2.1" + prebuild-install "^2.0.0" + keccakjs@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.1.tgz#1d633af907ef305bbf9f2fa616d56c44561dfa4d" @@ -4402,6 +4466,12 @@ strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" +strip-hex-prefix@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" + dependencies: + is-hex-prefixed "1.0.0" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -4689,6 +4759,10 @@ url-parse-lax@^1.0.0: dependencies: prepend-http "^1.0.1" +utf8@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.2.tgz#1fa0d9270e9be850d9b05027f63519bf46457d96" + util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -4730,6 +4804,16 @@ vows@0.5.13: dependencies: eyes ">=0.1.6" +web3@^0.18.4: + version "0.18.4" + resolved "https://registry.yarnpkg.com/web3/-/web3-0.18.4.tgz#81ec1784145491f2eaa8955b31c06049e07c5e7d" + dependencies: + bignumber.js "git+https://github.com/debris/bignumber.js.git#94d7146671b9719e00a09c29b01a691bc85048c2" + crypto-js "^3.1.4" + utf8 "^2.1.1" + xhr2 "*" + xmlhttprequest "*" + which@^1.2.8, which@^1.2.9: version "1.2.14" resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" @@ -4825,10 +4909,18 @@ xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" +xhr2@*: + version "0.1.4" + resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" + xmlhttprequest-ssl@1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" +xmlhttprequest@*: + version "1.8.0" + resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943"