diff --git a/providers/index.js b/providers/index.js index fe4ae90f5..ad376a4eb 100644 --- a/providers/index.js +++ b/providers/index.js @@ -6,6 +6,7 @@ var EtherscanProvider = require('./etherscan-provider.js'); var FallbackProvider = require('./fallback-provider.js'); var InfuraProvider = require('./infura-provider.js'); var JsonRpcProvider = require('./json-rpc-provider.js'); +var Web3Provider = require('./web3-provider.js'); function getDefaultProvider(network) { return new FallbackProvider([ @@ -19,6 +20,7 @@ module.exports = { FallbackProvider: FallbackProvider, InfuraProvider: InfuraProvider, JsonRpcProvider: JsonRpcProvider, + Web3Provider: Web3Provider, isProvider: Provider.isProvider, diff --git a/providers/json-rpc-provider.js b/providers/json-rpc-provider.js index 63e0183d5..f96225380 100644 --- a/providers/json-rpc-provider.js +++ b/providers/json-rpc-provider.js @@ -54,6 +54,12 @@ function getTransaction(transaction) { result[key] = stripHexZeros(result[key]); }); + // Transform "gasLimit" to "gas" + if (result.gasLimit != null && result.gas == null) { + result.gas = result.gasLimit; + delete result.gasLimit; + } + return result; } @@ -208,4 +214,8 @@ utils.defineProperty(JsonRpcProvider.prototype, '_stopPending', function() { this._pendingFilter = null; }); +utils.defineProperty(JsonRpcProvider, '_hexlifyTransaction', function(transaction) { + return getTransaction(transaction); +}); + module.exports = JsonRpcProvider; diff --git a/providers/package.json b/providers/package.json index fdbdddb55..5fc01b4f6 100644 --- a/providers/package.json +++ b/providers/package.json @@ -1,6 +1,6 @@ { "name": "ethers-providers", - "version": "2.1.16", + "version": "2.1.17", "description": "Service provider for Ethereum wallet library.", "bugs": { "url": "http://github.com/ethers-io/ethers.js/issues", diff --git a/providers/web3-provider.js b/providers/web3-provider.js new file mode 100644 index 000000000..ec9e65771 --- /dev/null +++ b/providers/web3-provider.js @@ -0,0 +1,159 @@ +'use strict'; + +var utils = require('ethers-utils'); + +var Provider = require('./provider'); +var JsonRpcProvider = require('./json-rpc-provider'); + + +function Web3Signer(provider, address) { + if (!(this instanceof Web3Signer)) { throw new Error('missing new'); } + utils.defineProperty(this, 'provider', provider); + + // Statically attach to a given address + if (address) { + utils.defineProperty(this, 'address', address); + utils.defineProperty(this, '_syncAddress', true); + + } else { + Object.defineProperty(this, 'address', { + enumerable: true, + get: function() { + throw new Error('unsupported sync operation; use getAddress'); + }, + writable: false + }); + utils.defineProperty(this, '_syncAddress', false); + } +} + +utils.defineProperty(Web3Signer.prototype, 'getAddress', function() { + if (this._syncAddress) { return Promise.resolve(this.address); } + + return this.provider.send('eth_accounts', []).then(function(accounts) { + if (accounts.length === 0) { + throw new Error('no account'); + } + return utils.getAddress(accounts[0]); + }); +}); + +utils.defineProperty(Web3Signer.prototype, 'getBalance', function(blockTag) { + var provider = this.provider; + return this.getAddress().then(function(address) { + return provider.getBalance(address, blockTag); + }); +}); + +utils.defineProperty(Web3Signer.prototype, 'getTransactionCount', function(blockTag) { + var provider = this.provider; + return this.getAddress().then(function(address) { + return provider.getTransactionCount(address, blockTag); + }); +}); + +utils.defineProperty(Web3Signer.prototype, 'sendTransaction', function(transaction) { + var provider = this.provider; + transaction = JsonRpcProvider._hexlifyTransaction(transaction); + return this.getAddress().then(function(address) { + transaction.from = address; + return provider.send('eth_sendTransaction', [ transaction ]).then(function(hash) { + return new Promise(function(resolve, reject) { + function check() { + provider.getTransaction(hash).then(function(transaction) { + if (!transaction) { + setTimeout(check, 1000); + return; + } + resolve(transaction); + }); + } + check(); + }); + }); + }); +}); + +utils.defineProperty(Web3Signer.prototype, 'signMessage', function(message) { + var provider = this.provider; + + var data = ((typeof(message) === 'string') ? utils.toUtf8Bytes(message): message); + return this.getAddress().then(function(address) { + return provider.send('eth_sign', [ address, utils.hexlify(data) ]); + }); +}); + +utils.defineProperty(Web3Signer.prototype, 'unlock', function(password) { + var provider = this.provider; + + return this.getAddress().then(function(address) { + return provider.send('personal_unlockAccount', [ address, password, null ]); + }); +}); + +/* +@TODO +utils.defineProperty(Web3Signer, 'onchange', { + +}); +*/ + +function Web3Provider(web3Provider, network) { + if (!(this instanceof Web3Provider)) { throw new Error('missing new'); } + + // HTTP has a host; IPC has a path. + var url = web3Provider.host || web3Provider.path || 'unknown'; + + // No need to support legacy parameters since this is post-legacy network + if (network == null) { + network = Provider.networks.homestead; + } else if (typeof(network) === 'string') { + network = Provider.networks[network]; + if (!network) { throw new Error('invalid network'); } + } + + JsonRpcProvider.call(this, url, network); + utils.defineProperty(this, '_web3Provider', web3Provider); +} +JsonRpcProvider.inherits(Web3Provider); + +utils.defineProperty(Web3Provider.prototype, 'getSigner', function(address) { + return new Web3Signer(this, address); +}); + +utils.defineProperty(Web3Provider.prototype, 'listAccounts', function() { + return this.send('eth_accounts', []).then(function(accounts) { + accounts.forEach(function(address, index) { + accounts[index] = utils.getAddress(address); + }); + return accounts; + }); +}); + +utils.defineProperty(Web3Provider.prototype, 'send', function(method, params) { + var provider = this._web3Provider; + return new Promise(function(resolve, reject) { + var request = { + method: method, + params: params, + id: 42, + jsonrpc: "2.0" + }; + provider.sendAsync(request, function(error, result) { + if (error) { + reject(error); + return; + } + if (result.error) { + var error = new Error(result.error.message); + error.code = result.error.code; + error.data = result.error.data; + reject(error); + return; + } + resolve(result.result); + }); + }); +}); + +module.exports = Web3Provider; diff --git a/tests/test-providers.js b/tests/test-providers.js index 94b3fcfcf..df4714f0a 100644 --- a/tests/test-providers.js +++ b/tests/test-providers.js @@ -1,6 +1,7 @@ 'use strict'; var assert = require('assert'); +var web3 = require('web3'); var providers = require('../providers'); @@ -258,12 +259,18 @@ function testProvider(providerName, networkName) { if (networkName === 'default') { if (providerName === 'getDefaultProvider') { provider = providers.getDefaultProvider(); + } else if (providerName === 'Web3Provider') { + var infuraUrl = (new providers.InfuraProvider()).url; + provider = new providers.Web3Provider(new web3.providers.HttpProvider(infuraUrl)); } else { provider = new providers[providerName](); } } else { if (providerName === 'getDefaultProvider') { provider = providers.getDefaultProvider(networkName); + } else if (providerName === 'Web3Provider') { + var infuraUrl = (new providers.InfuraProvider(networkName)).url; + provider = new providers.Web3Provider(new web3.providers.HttpProvider(infuraUrl), networkName); } else { provider = new providers[providerName](networkName); } @@ -354,7 +361,7 @@ function testProvider(providerName, networkName) { } ['default', 'homestead', 'ropsten', 'rinkeby', 'kovan'].forEach(function(networkName) { - ['getDefaultProvider', 'InfuraProvider', 'EtherscanProvider'].forEach(function(providerName) { + ['getDefaultProvider', 'InfuraProvider', 'EtherscanProvider', 'Web3Provider'].forEach(function(providerName) { // HACK! Etherscan is being cloudflare heavy right now and I need // to release a new version; temporarily turning off these tests