Abstracted providers and added etherscan provider.

This commit is contained in:
ricmoo 2016-08-03 19:36:44 -04:00
parent 1db36b3132
commit 1b0dc18bdc
8 changed files with 664 additions and 241 deletions

317
dist/ethers-wallet.js vendored
View File

@ -6,6 +6,7 @@ var rlp = require('rlp');
var scrypt = require('scrypt-js'); var scrypt = require('scrypt-js');
var Contract = require('./lib/contract.js'); var Contract = require('./lib/contract.js');
var providers = require('./lib/providers.js');
var secretStorage = require('./lib/secret-storage.js'); var secretStorage = require('./lib/secret-storage.js');
var Randomish = require('./lib/randomish.js'); var Randomish = require('./lib/randomish.js');
var SigningKey = require('./lib/signing-key.js'); var SigningKey = require('./lib/signing-key.js');
@ -37,6 +38,7 @@ module.exports = Wallet;
utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63'); utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63');
//utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress); //utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress);
//utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress); //utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress);
@ -81,12 +83,15 @@ utils.defineProperty(Wallet, 'summonBrainWallet', function(username, password, c
}); });
utils.defineProperty(Wallet, 'providers', providers);
utils.defineProperty(Wallet, 'randomish', new Randomish()); utils.defineProperty(Wallet, 'randomish', new Randomish());
module.exports = Wallet; module.exports = Wallet;
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./lib/contract.js":4,"./lib/randomish.js":6,"./lib/secret-storage.js":7,"./lib/signing-key.js":8,"./lib/utils.js":9,"./lib/wallet.js":10,"buffer":40,"rlp":83,"scrypt-js":84}],2:[function(require,module,exports){ },{"./lib/contract.js":4,"./lib/providers.js":5,"./lib/randomish.js":6,"./lib/secret-storage.js":7,"./lib/signing-key.js":8,"./lib/utils.js":9,"./lib/wallet.js":10,"buffer":40,"rlp":84,"scrypt-js":85}],2:[function(require,module,exports){
(function (global,Buffer){ (function (global,Buffer){
'use strict'; 'use strict';
@ -802,16 +807,29 @@ module.exports = Contract;
},{"./utils.js":9,"buffer":40}],5:[function(require,module,exports){ },{"./utils.js":9,"buffer":40}],5:[function(require,module,exports){
'use strict'; 'use strict';
var utils = require('./utils.js'); var inherits = require('inherits');
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
var SigningKey = require('./signing-key.js');
var utils = require('./utils.js');
// The required methods a provider must support
var methods = [
'getBalance',
'getTransactionCount',
'getGasPrice',
'sendTransaction',
'call',
'estimateGas'
];
// Manages JSON-RPC to an Ethereum node
function Web3Connector(provider) { function Web3Connector(provider) {
if (!(this instanceof Web3Connector)) { throw new Error('missing new'); } if (!(this instanceof Web3Connector)) { throw new Error('missing new'); }
var nextMessageId = 1; var nextMessageId = 1;
utils.defineProperty(this, 'sendMessage', function(method, params) { utils.defineProperty(this, 'sendMessage', function(method, params) {
//console.log('mm', method, params);
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
provider.sendAsync({ provider.sendAsync({
id: (nextMessageId++), id: (nextMessageId++),
@ -836,6 +854,7 @@ function Web3Connector(provider) {
}); });
} }
// Mimics Web3 interface
function rpcSendAsync(url) { function rpcSendAsync(url) {
return { return {
sendAsync: function(payload, callback) { sendAsync: function(payload, callback) {
@ -852,6 +871,7 @@ function rpcSendAsync(url) {
try { try {
callback(null, JSON.parse(result)); callback(null, JSON.parse(result));
} catch (error) { } catch (error) {
console.log(error);
callback(new Error('invalid response')); callback(new Error('invalid response'));
} }
}; };
@ -867,39 +887,9 @@ function rpcSendAsync(url) {
} }
} }
function SendAsyncProvider(sendAsync) {
function Provider(provider) { if (!(this instanceof SendAsyncProvider)) { throw new Error('missing new'); }
if (!(this instanceof Provider)) { throw new Error('missing new'); } utils.defineProperty(this, 'client', new Web3Connector(sendAsync));
var client = null;
if (typeof(provider) === 'string') {
// An RPC URL
if (provider.substring(0, 7) === 'http://') {
client = new Web3Connector(rpcSendAsync(provider));
// An ethers.io URL
} else if (provider.substring(0, 5) === 'ws://' || provider.substirng(0, 6) === 'wss://') {
//client =
// Etherscan
} else if (string === 'testnet.etherscan.io' || string === 'etherscan.io') {
// client =
}
// A Web3 Instance
} else if (provider.currentProvider && provider.currentProvider.sendAsync) {
client = new Web3Connector(provider.currentProvider);
// A Web3 Provider
} else if (provider.sendAsync) {
client = new Web3Connector(provider);
}
if (!client) { throw new Error('invalid connector'); }
utils.defineProperty(this, 'client', client);
} }
@ -934,34 +924,201 @@ function postProcess(client, method, params, makeBN) {
}); });
} }
utils.defineProperty(Provider.prototype, 'getBalance', function(address, blockNumber) { utils.defineProperty(SendAsyncProvider.prototype, 'getBalance', function(address, blockNumber) {
address = SigningKey.getAddress(address);
return postProcess(this.client, 'eth_getBalance', [address, validBlock(blockNumber)], true); return postProcess(this.client, 'eth_getBalance', [address, validBlock(blockNumber)], true);
}); });
utils.defineProperty(Provider.prototype, 'getTransactionCount', function(address, blockNumber) { utils.defineProperty(SendAsyncProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
address = SigningKey.getAddress(address);
return postProcess(this.client, 'eth_getTransactionCount', [address, validBlock(blockNumber)], false); return postProcess(this.client, 'eth_getTransactionCount', [address, validBlock(blockNumber)], false);
}); });
utils.defineProperty(Provider.prototype, 'getGasPrice', function() { utils.defineProperty(SendAsyncProvider.prototype, 'getGasPrice', function() {
return postProcess(this.client, 'eth_gasPrice', [], true); return postProcess(this.client, 'eth_gasPrice', [], true);
}); });
utils.defineProperty(Provider.prototype, 'sendTransaction', function(signedTransaction) { utils.defineProperty(SendAsyncProvider.prototype, 'sendTransaction', function(signedTransaction) {
if (!utils.isHexString(signedTransaction)) { throw new Error('invalid transaction'); }
return this.client.sendMessage('eth_sendRawTransaction', [signedTransaction]); return this.client.sendMessage('eth_sendRawTransaction', [signedTransaction]);
}); });
utils.defineProperty(Provider.prototype, 'call', function(transaction) { utils.defineProperty(SendAsyncProvider.prototype, 'call', function(transaction) {
// @TODO: check validTransaction?
return this.client.sendMessage('eth_call', [transaction]); return this.client.sendMessage('eth_call', [transaction]);
}); });
utils.defineProperty(Provider.prototype, 'estimateGas', function(transaction) { utils.defineProperty(SendAsyncProvider.prototype, 'estimateGas', function(transaction) {
// @TODO: check validTransaction?
return postProcess(this.client, 'eth_estimateGas', [transaction], true); return postProcess(this.client, 'eth_estimateGas', [transaction], true);
}); });
module.exports = Provider; var providers = {};
},{"./utils.js":9,"xmlhttprequest":3}],6:[function(require,module,exports){
function HttpProvider(url) {
if (!(this instanceof HttpProvider)) { throw new Error('missing new'); }
SendAsyncProvider.call(this, rpcSendAsync(url));
}
inherits(HttpProvider, SendAsyncProvider);
utils.defineProperty(providers, 'HttpProvider', HttpProvider);
function Web3Provider(provider) {
if (!(this instanceof Web3Provider)) { throw new Error('missing new'); }
if (provider.currentProvider) { provider = provider.currentProvider; }
if (!provider.sendAsync) { throw new Error('invalid provider'); }
SendAsyncProvider.call(this, provider);
}
inherits(Web3Provider, SendAsyncProvider);
utils.defineProperty(providers, 'Web3Provider', Web3Provider);
function base10ToBN(value) {
return new utils.BN(value);
}
function hexToBN(value) {
return new utils.BN(ensureHex(value).substring(2), 16);
}
function hexToNumber(value) {
if (!utils.isHexString(value)) { throw new Error('invalid hex string'); }
return parseInt(value.substring(2), 16);
}
function ensureHex(value) {
if (!utils.isHexString(value)) { throw new Error('invalid hex string'); }
return value;
}
function ensureTxid(value) {
if (!utils.isHexString(value, 32)) { throw new Error('invalid hex string'); }
return value;
}
function getGasPrice(value) {
if (!value || !value.transactions || value.transactions.length === 0) {
throw new Error('invalid response');
}
console.log(value.transactions[0].gasPrice)
return hexToBN(value.transactions[0].gasPrice);
}
function EtherscanProvider(options) {
if (!(this instanceof EtherscanProvider)) { throw new Error('missing new'); }
if (!options) { options = {}; }
var testnet = options.testnet;
var apiKey = options.apiKey;
utils.defineProperty(this, 'testnet', testnet);
utils.defineProperty(this, 'apiKey', apiKey);
utils.defineProperty(this, '_send', function(query, check) {
var url = (testnet ? 'https://testnet.etherscan.io/api?': 'https://etherscan.io/api?');
url += query;
if (apiKey) { url += 'apikey=' + apiKey; }
//console.log('URL', url);
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onreadystatechange = function() {
if (request.readyState !== 4) { return; }
var result = request.responseText;
//console.log(result);
try {
result = JSON.parse(result);
if (result.message) {
if (result.message === 'OK') {
resolve(check(result.result));
} else {
reject(new Error('invalid response'));
}
} else {
if (result.error) {
console.log(result.error);
reject(new Error('invalid response'));
} else {
resolve(check(result.result));
}
}
} catch (error) {
console.log(error);
reject(new Error('invalid response'));
}
}
try {
request.send();
} catch (error) {
var connectionError = new Error('connection error');
connectionError.error = error;
reject(connectionError);
}
});
});
}
utils.defineProperty(providers, 'EtherscanProvider', EtherscanProvider);
utils.defineProperty(EtherscanProvider.prototype, 'getBalance', function(address, blockNumber) {
address = SigningKey.getAddress(address);
blockNumber = validBlock(blockNumber);
var query = ('module=account&action=balance&address=' + address + '&tag=' + blockNumber);
return this._send(query, base10ToBN);
});
utils.defineProperty(EtherscanProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
address = SigningKey.getAddress(address);
blockNumber = validBlock(blockNumber);
var query = ('module=proxy&action=eth_getTransactionCount&address=' + address + '&tag=' + blockNumber);
return this._send(query, hexToNumber);
});
utils.defineProperty(EtherscanProvider.prototype, 'getGasPrice', function() {
/* This doesn't work, over-estimates gas if current block was anxious
var query = ('module=proxy&action=eth_getBlockByNumber&tag=latest&boolean=true');
return this._send(query, getGasPrice);
*/
throw new Error('etherscan does not support gasPrice');
});
utils.defineProperty(EtherscanProvider.prototype, 'sendTransaction', function(signedTransaction) {
if (!utils.isHexString(signedTransaction)) { throw new Error('invalid transaction'); }
var query = ('module=proxy&action=eth_sendRawTransaction&hex=' + signedTransaction);
return this._send(query, ensureTxid);
});
utils.defineProperty(EtherscanProvider.prototype, 'call', function(transaction) {
var address = SigningKey.getAddress(transaction.to);
var data = transaction.data;
if (!utils.isHexString(data)) { throw new Error('invalid data'); }
var query = ('module=proxy&action=eth_call&to=' + address + '&data=' + data);
return this._send(query, ensureHex);
});
utils.defineProperty(EtherscanProvider.prototype, 'estimateGas', function(transaction) {
throw new Error('etherscan does not support estimation');
});
utils.defineProperty(providers, 'isProvider', function(provider) {
if (!provider) { return false; }
for (var i = 0; i < methods; i++) {
if (typeof(provider[methods[i]]) !== 'function') {
return false;
}
}
return true;
});
module.exports = providers;
},{"./signing-key.js":8,"./utils.js":9,"inherits":67,"xmlhttprequest":3}],6:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
'use strict'; 'use strict';
@ -1379,7 +1536,7 @@ utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, op
module.exports = secretStorage; module.exports = secretStorage;
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./randomish.js":6,"./signing-key.js":8,"./utils.js":9,"aes-js":11,"buffer":40,"pbkdf2":67,"scrypt-js":84,"uuid":86}],8:[function(require,module,exports){ },{"./randomish.js":6,"./signing-key.js":8,"./utils.js":9,"aes-js":11,"buffer":40,"pbkdf2":68,"scrypt-js":85,"uuid":87}],8:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
'use strict'; 'use strict';
@ -1809,13 +1966,13 @@ module.exports = {
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"../node_modules/elliptic/node_modules/bn.js/lib/bn.js":28,"../node_modules/elliptic/node_modules/hash.js/lib/hash.js":30,"buffer":40}],10:[function(require,module,exports){ },{"../node_modules/elliptic/node_modules/bn.js/lib/bn.js":28,"../node_modules/elliptic/node_modules/hash.js/lib/hash.js":30,"buffer":40}],10:[function(require,module,exports){
(function (Buffer){ (function (global,Buffer){
'use strict'; 'use strict';
var rlp = require('rlp'); var rlp = require('rlp');
var Contract = require('./contract.js'); var Contract = require('./contract.js');
var Provider = require('./provider.js'); var providers = require('./providers.js');
var SigningKey = require('./signing-key.js'); var SigningKey = require('./signing-key.js');
var utils = require('./utils.js'); var utils = require('./utils.js');
@ -1844,7 +2001,8 @@ function Wallet(privateKey, provider) {
enumerable: true, enumerable: true,
get: function() { return provider; }, get: function() { return provider; },
set: function(value) { set: function(value) {
provider = new Provider(provider); if (!providers.isProvider(value)) { throw new Error('invalid provider'); }
provider = value;
} }
}); });
@ -1856,8 +2014,21 @@ function Wallet(privateKey, provider) {
}, },
}); });
if (provider) { if (provider !== null) {
this.provider = provider;
// If no provider was provided, check for metamask or ilk
if (provider === undefined) {
if (global.web3 && global.web3.currentProvider && global.web3.currentProvider.sendAsync) {
this.provider = new providers.Web3Provider(global.web3.currentProvider);
}
// An Ethereum RPC node
} else if (typeof(provider) === 'string' && provider.match(/^https?:\/\//)) {
this.provider = new providers.HttpProvider(provider);
} else {
this.provider = provider;
}
} }
utils.defineProperty(this, 'address', signingKey.address); utils.defineProperty(this, 'address', signingKey.address);
@ -2091,8 +2262,8 @@ utils.defineProperty(Wallet, 'parseEther', function(ether) {
module.exports = Wallet; module.exports = Wallet;
}).call(this,require("buffer").Buffer) }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer)
},{"./contract.js":4,"./provider.js":5,"./signing-key.js":8,"./utils.js":9,"buffer":40,"rlp":83}],11:[function(require,module,exports){ },{"./contract.js":4,"./providers.js":5,"./signing-key.js":8,"./utils.js":9,"buffer":40,"rlp":84}],11:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
"use strict"; "use strict";
@ -17294,6 +17465,8 @@ function hasOwnProperty(obj, prop) {
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./support/isBuffer":65,"_process":47,"inherits":45}],67:[function(require,module,exports){ },{"./support/isBuffer":65,"_process":47,"inherits":45}],67:[function(require,module,exports){
arguments[4][36][0].apply(exports,arguments)
},{"dup":36}],68:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
var createHmac = require('create-hmac') var createHmac = require('create-hmac')
var MAX_ALLOC = Math.pow(2, 30) - 1 // default in iojs var MAX_ALLOC = Math.pow(2, 30) - 1 // default in iojs
@ -17377,7 +17550,7 @@ function pbkdf2Sync (password, salt, iterations, keylen, digest) {
} }
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40,"create-hmac":68}],68:[function(require,module,exports){ },{"buffer":40,"create-hmac":69}],69:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
'use strict'; 'use strict';
var createHash = require('create-hash/browser'); var createHash = require('create-hash/browser');
@ -17449,7 +17622,7 @@ module.exports = function createHmac(alg, key) {
} }
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40,"create-hash/browser":69,"inherits":82,"stream":63}],69:[function(require,module,exports){ },{"buffer":40,"create-hash/browser":70,"inherits":83,"stream":63}],70:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
'use strict'; 'use strict';
var inherits = require('inherits') var inherits = require('inherits')
@ -17505,7 +17678,7 @@ module.exports = function createHash (alg) {
} }
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./md5":71,"buffer":40,"cipher-base":72,"inherits":82,"ripemd160":73,"sha.js":75}],70:[function(require,module,exports){ },{"./md5":72,"buffer":40,"cipher-base":73,"inherits":83,"ripemd160":74,"sha.js":76}],71:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
'use strict'; 'use strict';
var intSize = 4; var intSize = 4;
@ -17542,7 +17715,7 @@ function hash(buf, fn, hashSize, bigEndian) {
} }
exports.hash = hash; exports.hash = hash;
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40}],71:[function(require,module,exports){ },{"buffer":40}],72:[function(require,module,exports){
'use strict'; 'use strict';
/* /*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
@ -17699,7 +17872,7 @@ function bit_rol(num, cnt)
module.exports = function md5(buf) { module.exports = function md5(buf) {
return helpers.hash(buf, core_md5, 16); return helpers.hash(buf, core_md5, 16);
}; };
},{"./helpers":70}],72:[function(require,module,exports){ },{"./helpers":71}],73:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
var Transform = require('stream').Transform var Transform = require('stream').Transform
var inherits = require('inherits') var inherits = require('inherits')
@ -17793,7 +17966,7 @@ CipherBase.prototype._toString = function (value, enc, final) {
} }
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40,"inherits":82,"stream":63,"string_decoder":64}],73:[function(require,module,exports){ },{"buffer":40,"inherits":83,"stream":63,"string_decoder":64}],74:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
/* /*
CryptoJS v3.1.2 CryptoJS v3.1.2
@ -18007,7 +18180,7 @@ function ripemd160 (message) {
module.exports = ripemd160 module.exports = ripemd160
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40}],74:[function(require,module,exports){ },{"buffer":40}],75:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
// prototype class for hash functions // prototype class for hash functions
function Hash (blockSize, finalSize) { function Hash (blockSize, finalSize) {
@ -18080,7 +18253,7 @@ Hash.prototype._update = function () {
module.exports = Hash module.exports = Hash
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40}],75:[function(require,module,exports){ },{"buffer":40}],76:[function(require,module,exports){
var exports = module.exports = function SHA (algorithm) { var exports = module.exports = function SHA (algorithm) {
algorithm = algorithm.toLowerCase() algorithm = algorithm.toLowerCase()
@ -18097,7 +18270,7 @@ exports.sha256 = require('./sha256')
exports.sha384 = require('./sha384') exports.sha384 = require('./sha384')
exports.sha512 = require('./sha512') exports.sha512 = require('./sha512')
},{"./sha":76,"./sha1":77,"./sha224":78,"./sha256":79,"./sha384":80,"./sha512":81}],76:[function(require,module,exports){ },{"./sha":77,"./sha1":78,"./sha224":79,"./sha256":80,"./sha384":81,"./sha512":82}],77:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
/* /*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-0, as defined * A JavaScript implementation of the Secure Hash Algorithm, SHA-0, as defined
@ -18194,7 +18367,7 @@ Sha.prototype._hash = function () {
module.exports = Sha module.exports = Sha
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"buffer":40,"inherits":82}],77:[function(require,module,exports){ },{"./hash":75,"buffer":40,"inherits":83}],78:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
/* /*
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
@ -18296,7 +18469,7 @@ Sha1.prototype._hash = function () {
module.exports = Sha1 module.exports = Sha1
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"buffer":40,"inherits":82}],78:[function(require,module,exports){ },{"./hash":75,"buffer":40,"inherits":83}],79:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
/** /**
* A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined
@ -18352,7 +18525,7 @@ Sha224.prototype._hash = function () {
module.exports = Sha224 module.exports = Sha224
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"./sha256":79,"buffer":40,"inherits":82}],79:[function(require,module,exports){ },{"./hash":75,"./sha256":80,"buffer":40,"inherits":83}],80:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
/** /**
* A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined * A JavaScript implementation of the Secure Hash Algorithm, SHA-256, as defined
@ -18490,7 +18663,7 @@ Sha256.prototype._hash = function () {
module.exports = Sha256 module.exports = Sha256
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"buffer":40,"inherits":82}],80:[function(require,module,exports){ },{"./hash":75,"buffer":40,"inherits":83}],81:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
var inherits = require('inherits') var inherits = require('inherits')
var SHA512 = require('./sha512') var SHA512 = require('./sha512')
@ -18550,7 +18723,7 @@ Sha384.prototype._hash = function () {
module.exports = Sha384 module.exports = Sha384
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"./sha512":81,"buffer":40,"inherits":82}],81:[function(require,module,exports){ },{"./hash":75,"./sha512":82,"buffer":40,"inherits":83}],82:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
var inherits = require('inherits') var inherits = require('inherits')
var Hash = require('./hash') var Hash = require('./hash')
@ -18813,9 +18986,9 @@ Sha512.prototype._hash = function () {
module.exports = Sha512 module.exports = Sha512
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"./hash":74,"buffer":40,"inherits":82}],82:[function(require,module,exports){ },{"./hash":75,"buffer":40,"inherits":83}],83:[function(require,module,exports){
arguments[4][36][0].apply(exports,arguments) arguments[4][36][0].apply(exports,arguments)
},{"dup":36}],83:[function(require,module,exports){ },{"dup":36}],84:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
const assert = require('assert') const assert = require('assert')
/** /**
@ -19048,7 +19221,7 @@ function toBuffer (v) {
} }
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"assert":38,"buffer":40}],84:[function(require,module,exports){ },{"assert":38,"buffer":40}],85:[function(require,module,exports){
(function (Buffer){ (function (Buffer){
"use strict"; "use strict";
@ -19510,7 +19683,7 @@ function toBuffer (v) {
})(this); })(this);
}).call(this,require("buffer").Buffer) }).call(this,require("buffer").Buffer)
},{"buffer":40}],85:[function(require,module,exports){ },{"buffer":40}],86:[function(require,module,exports){
(function (global){ (function (global){
var rng; var rng;
@ -19545,7 +19718,7 @@ module.exports = rng;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}],86:[function(require,module,exports){ },{}],87:[function(require,module,exports){
// uuid.js // uuid.js
// //
// Copyright (c) 2010-2012 Robert Kieffer // Copyright (c) 2010-2012 Robert Kieffer
@ -19730,5 +19903,5 @@ uuid.unparse = unparse;
module.exports = uuid; module.exports = uuid;
},{"./rng":85}]},{},[1])(1) },{"./rng":86}]},{},[1])(1)
}); });

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@ var rlp = require('rlp');
var scrypt = require('scrypt-js'); var scrypt = require('scrypt-js');
var Contract = require('./lib/contract.js'); var Contract = require('./lib/contract.js');
var providers = require('./lib/providers.js');
var secretStorage = require('./lib/secret-storage.js'); var secretStorage = require('./lib/secret-storage.js');
var Randomish = require('./lib/randomish.js'); var Randomish = require('./lib/randomish.js');
var SigningKey = require('./lib/signing-key.js'); var SigningKey = require('./lib/signing-key.js');
@ -35,6 +36,7 @@ module.exports = Wallet;
utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63'); utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63');
//utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress); //utils.defineProperty(Wallet, 'getAddress', SigningKey.getAddress);
//utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress); //utils.defineProperty(Wallet, 'getIcapAddress', SigningKey.getIcapAddress);
@ -79,6 +81,9 @@ utils.defineProperty(Wallet, 'summonBrainWallet', function(username, password, c
}); });
utils.defineProperty(Wallet, 'providers', providers);
utils.defineProperty(Wallet, 'randomish', new Randomish()); utils.defineProperty(Wallet, 'randomish', new Randomish());
module.exports = Wallet; module.exports = Wallet;

View File

@ -1,160 +0,0 @@
'use strict';
var utils = require('./utils.js');
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
function Web3Connector(provider) {
if (!(this instanceof Web3Connector)) { throw new Error('missing new'); }
var nextMessageId = 1;
utils.defineProperty(this, 'sendMessage', function(method, params) {
//console.log('mm', method, params);
return new Promise(function(resolve, reject) {
provider.sendAsync({
id: (nextMessageId++),
jsonrpc: '2.0',
method: method,
params: params
}, function(error, result) {
if (error) {
reject(error);
} else {
if (result.error) {
var error = new Error(result.error.message);
error.code = result.error.code;
error.data = result.error.data;
reject(error);
} else {
resolve(result.result);
}
}
});
});
});
}
function rpcSendAsync(url) {
return {
sendAsync: function(payload, callback) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type','application/json');
request.onreadystatechange = function() {
if (request.readyState !== 4) { return; }
if (typeof(callback) !== 'function') { return; }
var result = request.responseText;
try {
callback(null, JSON.parse(result));
} catch (error) {
callback(new Error('invalid response'));
}
};
try {
request.send(JSON.stringify(payload));
} catch (error) {
var connectionError = new Error('connection error');
connectionError.error = error;
callback(connectionError);
}
}
}
}
function Provider(provider) {
if (!(this instanceof Provider)) { throw new Error('missing new'); }
var client = null;
if (typeof(provider) === 'string') {
// An RPC URL
if (provider.substring(0, 7) === 'http://') {
client = new Web3Connector(rpcSendAsync(provider));
// An ethers.io URL
} else if (provider.substring(0, 5) === 'ws://' || provider.substirng(0, 6) === 'wss://') {
//client =
// Etherscan
} else if (string === 'testnet.etherscan.io' || string === 'etherscan.io') {
// client =
}
// A Web3 Instance
} else if (provider.currentProvider && provider.currentProvider.sendAsync) {
client = new Web3Connector(provider.currentProvider);
// A Web3 Provider
} else if (provider.sendAsync) {
client = new Web3Connector(provider);
}
if (!client) { throw new Error('invalid connector'); }
utils.defineProperty(this, 'client', client);
}
function validBlock(value) {
if (value == null) { return 'latest'; }
if (value === 'latest' || value === 'pending') { return value; }
if (typeof(value) === 'number' && value == parseInt(value)) {
return parseInt(value);
}
throw new Error('invalid blockNumber');
}
function postProcess(client, method, params, makeBN) {
return new Promise(function(resolve, reject) {
client.sendMessage(method, params).then(function (result) {
if (!utils.isHexString(result)) {
reject(new Error('invalid server response'));
} else {
result = result.substring(2);
if (makeBN) {
result = new utils.BN(result, 16);
} else {
result = parseInt(result, 16);
}
resolve(result);
}
}, function(error) {
reject(error);
});
});
}
utils.defineProperty(Provider.prototype, 'getBalance', function(address, blockNumber) {
return postProcess(this.client, 'eth_getBalance', [address, validBlock(blockNumber)], true);
});
utils.defineProperty(Provider.prototype, 'getTransactionCount', function(address, blockNumber) {
return postProcess(this.client, 'eth_getTransactionCount', [address, validBlock(blockNumber)], false);
});
utils.defineProperty(Provider.prototype, 'getGasPrice', function() {
return postProcess(this.client, 'eth_gasPrice', [], true);
});
utils.defineProperty(Provider.prototype, 'sendTransaction', function(signedTransaction) {
return this.client.sendMessage('eth_sendRawTransaction', [signedTransaction]);
});
utils.defineProperty(Provider.prototype, 'call', function(transaction) {
return this.client.sendMessage('eth_call', [transaction]);
});
utils.defineProperty(Provider.prototype, 'estimateGas', function(transaction) {
return postProcess(this.client, 'eth_estimateGas', [transaction], true);
});
module.exports = Provider;

312
lib/providers.js Normal file
View File

@ -0,0 +1,312 @@
'use strict';
var inherits = require('inherits');
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
var SigningKey = require('./signing-key.js');
var utils = require('./utils.js');
// The required methods a provider must support
var methods = [
'getBalance',
'getTransactionCount',
'getGasPrice',
'sendTransaction',
'call',
'estimateGas'
];
// Manages JSON-RPC to an Ethereum node
function Web3Connector(provider) {
if (!(this instanceof Web3Connector)) { throw new Error('missing new'); }
var nextMessageId = 1;
utils.defineProperty(this, 'sendMessage', function(method, params) {
return new Promise(function(resolve, reject) {
provider.sendAsync({
id: (nextMessageId++),
jsonrpc: '2.0',
method: method,
params: params
}, function(error, result) {
if (error) {
reject(error);
} else {
if (result.error) {
var error = new Error(result.error.message);
error.code = result.error.code;
error.data = result.error.data;
reject(error);
} else {
resolve(result.result);
}
}
});
});
});
}
// Mimics Web3 interface
function rpcSendAsync(url) {
return {
sendAsync: function(payload, callback) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type','application/json');
request.onreadystatechange = function() {
if (request.readyState !== 4) { return; }
if (typeof(callback) !== 'function') { return; }
var result = request.responseText;
try {
callback(null, JSON.parse(result));
} catch (error) {
console.log(error);
callback(new Error('invalid response'));
}
};
try {
request.send(JSON.stringify(payload));
} catch (error) {
var connectionError = new Error('connection error');
connectionError.error = error;
callback(connectionError);
}
}
}
}
function SendAsyncProvider(sendAsync) {
if (!(this instanceof SendAsyncProvider)) { throw new Error('missing new'); }
utils.defineProperty(this, 'client', new Web3Connector(sendAsync));
}
function validBlock(value) {
if (value == null) { return 'latest'; }
if (value === 'latest' || value === 'pending') { return value; }
if (typeof(value) === 'number' && value == parseInt(value)) {
return parseInt(value);
}
throw new Error('invalid blockNumber');
}
function postProcess(client, method, params, makeBN) {
return new Promise(function(resolve, reject) {
client.sendMessage(method, params).then(function (result) {
if (!utils.isHexString(result)) {
reject(new Error('invalid server response'));
} else {
result = result.substring(2);
if (makeBN) {
result = new utils.BN(result, 16);
} else {
result = parseInt(result, 16);
}
resolve(result);
}
}, function(error) {
reject(error);
});
});
}
utils.defineProperty(SendAsyncProvider.prototype, 'getBalance', function(address, blockNumber) {
address = SigningKey.getAddress(address);
return postProcess(this.client, 'eth_getBalance', [address, validBlock(blockNumber)], true);
});
utils.defineProperty(SendAsyncProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
address = SigningKey.getAddress(address);
return postProcess(this.client, 'eth_getTransactionCount', [address, validBlock(blockNumber)], false);
});
utils.defineProperty(SendAsyncProvider.prototype, 'getGasPrice', function() {
return postProcess(this.client, 'eth_gasPrice', [], true);
});
utils.defineProperty(SendAsyncProvider.prototype, 'sendTransaction', function(signedTransaction) {
if (!utils.isHexString(signedTransaction)) { throw new Error('invalid transaction'); }
return this.client.sendMessage('eth_sendRawTransaction', [signedTransaction]);
});
utils.defineProperty(SendAsyncProvider.prototype, 'call', function(transaction) {
// @TODO: check validTransaction?
return this.client.sendMessage('eth_call', [transaction]);
});
utils.defineProperty(SendAsyncProvider.prototype, 'estimateGas', function(transaction) {
// @TODO: check validTransaction?
return postProcess(this.client, 'eth_estimateGas', [transaction], true);
});
var providers = {};
function HttpProvider(url) {
if (!(this instanceof HttpProvider)) { throw new Error('missing new'); }
SendAsyncProvider.call(this, rpcSendAsync(url));
}
inherits(HttpProvider, SendAsyncProvider);
utils.defineProperty(providers, 'HttpProvider', HttpProvider);
function Web3Provider(provider) {
if (!(this instanceof Web3Provider)) { throw new Error('missing new'); }
if (provider.currentProvider) { provider = provider.currentProvider; }
if (!provider.sendAsync) { throw new Error('invalid provider'); }
SendAsyncProvider.call(this, provider);
}
inherits(Web3Provider, SendAsyncProvider);
utils.defineProperty(providers, 'Web3Provider', Web3Provider);
function base10ToBN(value) {
return new utils.BN(value);
}
function hexToBN(value) {
return new utils.BN(ensureHex(value).substring(2), 16);
}
function hexToNumber(value) {
if (!utils.isHexString(value)) { throw new Error('invalid hex string'); }
return parseInt(value.substring(2), 16);
}
function ensureHex(value) {
if (!utils.isHexString(value)) { throw new Error('invalid hex string'); }
return value;
}
function ensureTxid(value) {
if (!utils.isHexString(value, 32)) { throw new Error('invalid hex string'); }
return value;
}
function getGasPrice(value) {
if (!value || !value.transactions || value.transactions.length === 0) {
throw new Error('invalid response');
}
console.log(value.transactions[0].gasPrice)
return hexToBN(value.transactions[0].gasPrice);
}
function EtherscanProvider(options) {
if (!(this instanceof EtherscanProvider)) { throw new Error('missing new'); }
if (!options) { options = {}; }
var testnet = options.testnet;
var apiKey = options.apiKey;
utils.defineProperty(this, 'testnet', testnet);
utils.defineProperty(this, 'apiKey', apiKey);
utils.defineProperty(this, '_send', function(query, check) {
var url = (testnet ? 'https://testnet.etherscan.io/api?': 'https://etherscan.io/api?');
url += query;
if (apiKey) { url += 'apikey=' + apiKey; }
//console.log('URL', url);
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onreadystatechange = function() {
if (request.readyState !== 4) { return; }
var result = request.responseText;
//console.log(result);
try {
result = JSON.parse(result);
if (result.message) {
if (result.message === 'OK') {
resolve(check(result.result));
} else {
reject(new Error('invalid response'));
}
} else {
if (result.error) {
console.log(result.error);
reject(new Error('invalid response'));
} else {
resolve(check(result.result));
}
}
} catch (error) {
console.log(error);
reject(new Error('invalid response'));
}
}
try {
request.send();
} catch (error) {
var connectionError = new Error('connection error');
connectionError.error = error;
reject(connectionError);
}
});
});
}
utils.defineProperty(providers, 'EtherscanProvider', EtherscanProvider);
utils.defineProperty(EtherscanProvider.prototype, 'getBalance', function(address, blockNumber) {
address = SigningKey.getAddress(address);
blockNumber = validBlock(blockNumber);
var query = ('module=account&action=balance&address=' + address + '&tag=' + blockNumber);
return this._send(query, base10ToBN);
});
utils.defineProperty(EtherscanProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
address = SigningKey.getAddress(address);
blockNumber = validBlock(blockNumber);
var query = ('module=proxy&action=eth_getTransactionCount&address=' + address + '&tag=' + blockNumber);
return this._send(query, hexToNumber);
});
utils.defineProperty(EtherscanProvider.prototype, 'getGasPrice', function() {
/* This doesn't work, over-estimates gas if current block was anxious
var query = ('module=proxy&action=eth_getBlockByNumber&tag=latest&boolean=true');
return this._send(query, getGasPrice);
*/
throw new Error('etherscan does not support gasPrice');
});
utils.defineProperty(EtherscanProvider.prototype, 'sendTransaction', function(signedTransaction) {
if (!utils.isHexString(signedTransaction)) { throw new Error('invalid transaction'); }
var query = ('module=proxy&action=eth_sendRawTransaction&hex=' + signedTransaction);
return this._send(query, ensureTxid);
});
utils.defineProperty(EtherscanProvider.prototype, 'call', function(transaction) {
var address = SigningKey.getAddress(transaction.to);
var data = transaction.data;
if (!utils.isHexString(data)) { throw new Error('invalid data'); }
var query = ('module=proxy&action=eth_call&to=' + address + '&data=' + data);
return this._send(query, ensureHex);
});
utils.defineProperty(EtherscanProvider.prototype, 'estimateGas', function(transaction) {
throw new Error('etherscan does not support estimation');
});
utils.defineProperty(providers, 'isProvider', function(provider) {
if (!provider) { return false; }
for (var i = 0; i < methods; i++) {
if (typeof(provider[methods[i]]) !== 'function') {
return false;
}
}
return true;
});
module.exports = providers;

View File

@ -3,7 +3,7 @@
var rlp = require('rlp'); var rlp = require('rlp');
var Contract = require('./contract.js'); var Contract = require('./contract.js');
var Provider = require('./provider.js'); var providers = require('./providers.js');
var SigningKey = require('./signing-key.js'); var SigningKey = require('./signing-key.js');
var utils = require('./utils.js'); var utils = require('./utils.js');
@ -32,7 +32,8 @@ function Wallet(privateKey, provider) {
enumerable: true, enumerable: true,
get: function() { return provider; }, get: function() { return provider; },
set: function(value) { set: function(value) {
provider = new Provider(provider); if (!providers.isProvider(value)) { throw new Error('invalid provider'); }
provider = value;
} }
}); });
@ -44,8 +45,21 @@ function Wallet(privateKey, provider) {
}, },
}); });
if (provider) { if (provider !== null) {
this.provider = provider;
// If no provider was provided, check for metamask or ilk
if (provider === undefined) {
if (global.web3 && global.web3.currentProvider && global.web3.currentProvider.sendAsync) {
this.provider = new providers.Web3Provider(global.web3.currentProvider);
}
// An Ethereum RPC node
} else if (typeof(provider) === 'string' && provider.match(/^https?:\/\//)) {
this.provider = new providers.HttpProvider(provider);
} else {
this.provider = provider;
}
} }
utils.defineProperty(this, 'address', signingKey.address); utils.defineProperty(this, 'address', signingKey.address);

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers-wallet", "name": "ethers-wallet",
"version": "0.0.4", "version": "0.0.5",
"description": "Ethereum wallet library.", "description": "Ethereum wallet library.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"aes-js": "2.0.0", "aes-js": "2.0.0",
"elliptic": "6.3.1", "elliptic": "6.3.1",
"inherits": "2.0.1",
"pbkdf2": "3.0.4", "pbkdf2": "3.0.4",
"rlp": "2.0.0", "rlp": "2.0.0",
"scrypt-js": "2.0.2", "scrypt-js": "2.0.2",

78
tests/test-providers.js Normal file
View File

@ -0,0 +1,78 @@
'use strict';
var Wallet = require('../index.js');
var Web3 = require('web3');
module.exports = function(test) {
var pending = [];
var otherGasPrice = null;
function checkGasPrice(value) {
if (otherGasPrice === null) {
otherGasPrice = value;
return true;
}
var same = otherGasPrice.eq(value);
if (!same) {
console.log('NOTE: This test case may have false positives; try again');
}
return same;
}
function checkMethod(provider, method, params, expectedValue) {
pending.push(new Promise(function(resolve, reject) {
provider[method].apply(provider, params).then(function(value) {
//console.log(provider, method, expectedValue, value);
if (expectedValue instanceof Wallet.utils.BN) {
test.ok(expectedValue.eq(value), 'Failed ' + method);
} else if (typeof(expectedValue) === 'function') {
test.ok(expectedValue(value), 'Failed ' + method);
} else {
test.equal(value, expectedValue, 'Failed ' + method);
}
resolve();
}, function(error) {
test.ok(false, 'Error - ' + error.message);
reject(error);
});
}));
}
function check(provider) {
checkMethod(
provider,
'getBalance', ['0x7357589f8e367c2C31F51242fB77B350A11830F3'],
new Wallet.utils.BN('436095820614148588744')
);
checkMethod(
provider,
'getTransactionCount', ['0x7357589f8e367c2C31F51242fB77B350A11830F3'],
1048598
);
// checkMethod(provider, 'getGasPrice', [], checkGasPrice);
checkMethod(
provider,
'call', [{to: '0xdfaf84077cF4bCECA4F79d167F47041Ed3006D5b', data: '0x20965255'}],
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000'
);
}
var url = 'http://localhost:8545';
var web3Provider = new Web3.providers.HttpProvider(url)
var web3 = new Web3(web3Provider)
check(new Wallet.providers.Web3Provider(web3Provider));
check(new Wallet.providers.Web3Provider(web3));
check(new Wallet.providers.HttpProvider(url));
check(new Wallet.providers.EtherscanProvider({testnet: true}));
Promise.all(pending).then(function(results) {
test.done();
}, function(error) {
console.log(error);
test.ok(false, 'Error occured: ' + error.message);
});
}
module.exports.testSelf = module.exports;