Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc2583ddd8 | ||
|
|
3be962f09d | ||
|
|
736b08e016 | ||
|
|
d8013cae37 | ||
|
|
b26b1b9c53 | ||
|
|
1c9c7b7a7d | ||
|
|
a9aaeefe24 | ||
|
|
8257e3f885 | ||
|
|
965c987761 | ||
|
|
142e5276fe | ||
|
|
351d1a2dad | ||
|
|
72424ea2d2 | ||
|
|
1e703bfdb4 | ||
|
|
dee0a4bf3e | ||
|
|
9c1d6051fb | ||
|
|
3c67736cbb | ||
|
|
c697a5bc81 | ||
|
|
0e9df5cb76 | ||
|
|
7a37dd6949 | ||
|
|
cecefd416d | ||
|
|
79f9047c21 | ||
|
|
f9f7469ccf | ||
|
|
1e5933eb45 | ||
|
|
a4e1f531b7 | ||
|
|
0a38d14930 | ||
|
|
4e7f2fa3d4 | ||
|
|
4b99bb579c | ||
|
|
0b59519c19 | ||
|
|
4d18119ccf | ||
|
|
4af236d3ac | ||
|
|
eddb9c28a8 | ||
|
|
40a6fdd95e | ||
|
|
3f80001e4d | ||
|
|
89082164ce | ||
|
|
9df7c2f37a | ||
|
|
840f510bf1 | ||
|
|
1bd95b127b | ||
|
|
97154e49a6 | ||
|
|
db757b5bbb | ||
|
|
af62f54042 | ||
|
|
1b0dc18bdc | ||
|
|
1db36b3132 | ||
|
|
805879a213 | ||
|
|
e1157f3a4c | ||
|
|
46e0866410 | ||
|
|
621c1ee74e | ||
|
|
b3f9070b39 | ||
|
|
14b15dcf5a | ||
|
|
e4c62d2939 | ||
|
|
b50c0bd615 | ||
|
|
e55ee253ab | ||
|
|
f81fa6e4f5 | ||
|
|
4785c650f3 | ||
|
|
be99fa858e | ||
|
|
196d4f6659 | ||
|
|
1233233c6c | ||
|
|
742bcc753f | ||
|
|
839c2a10aa | ||
|
|
b298d41c22 | ||
|
|
c70a9f61cb | ||
|
|
6440d8e69d | ||
|
|
97dcd4d450 | ||
|
|
b8d4514dd0 | ||
|
|
0ceee56de3 | ||
|
|
024cc1806f | ||
|
|
5e421d2636 | ||
|
|
f71ea72d54 | ||
|
|
d65379f6c4 | ||
|
|
06281e85c4 | ||
|
|
5dc2621a32 | ||
|
|
c024f88ce1 | ||
|
|
b5e6ac7db9 | ||
|
|
60732f8243 | ||
|
|
4a557349a3 | ||
|
|
5bf0c310d3 | ||
|
|
99ec103e97 | ||
|
|
829505d548 | ||
|
|
5c9ee7f2a5 | ||
|
|
fa0d54966d | ||
|
|
401dd5162d | ||
|
|
2dee42449a | ||
|
|
224e8aff07 | ||
|
|
b5710cd710 | ||
|
|
8033a8f94c | ||
|
|
4bcef804b6 | ||
|
|
66a5b0ecdc | ||
|
|
2605e7de40 | ||
|
|
f50f171223 | ||
|
|
7626b541f5 | ||
|
|
24c7fc533a | ||
|
|
21fdfe9581 | ||
|
|
ca8ee1c0e3 | ||
|
|
0980b864fa | ||
|
|
d0a822f02a | ||
|
|
4dbce955ee |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
22
LICENSE.txt
Normal file
22
LICENSE.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Richard Moore
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
293
README.md
293
README.md
@@ -1,19 +1,46 @@
|
||||
ethers-wallet
|
||||
=============
|
||||
|
||||
Complete Ethereum wallet implementation in JavaScript.
|
||||
[](https://badge.fury.io/js/ethers-wallet)
|
||||
|
||||
Features
|
||||
- Keep your private keys in the browser
|
||||
- Small (~155kb compressed; hopefully under 100kb soon)
|
||||
- MIT licensed (with a few exceptions, which we are migrating off of; see below)
|
||||
Complete Ethereum wallet implementation and utilities in JavaScript.
|
||||
|
||||
*NOTE: This is still very beta; please only use it on the testnet for now, or with VERY small amounts of ether on the livenet that you are willing to lose due to bugs.*
|
||||
**Features:**
|
||||
|
||||
- Keep your private keys in your browser
|
||||
- Import and export JSON wallets (Geth and crowdsale) and brain wallets
|
||||
- Meta-classes create JavaScript objects from any contract ABI
|
||||
- Connect to Ethereum nodes over RPC, injected Web3 or [Etherscan](https://etherscan.io)
|
||||
- Small (~100kb compressed; 290kb uncompressed)
|
||||
- MIT licensed (with one exception, which we are migrating off of; see below)
|
||||
|
||||
**NOTE: This is still very beta; please only use it on the testnet for now, or with VERY small amounts of ether on the livenet that you are willing to lose due to bugs.**
|
||||
|
||||
|
||||
Wallet API
|
||||
Installing
|
||||
----------
|
||||
|
||||
To use in a browser:
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="https://rawgit.com/ethers-io/ethers-wallet/master/dist/ethers-wallet.min.js"></script>
|
||||
```
|
||||
|
||||
To use in [node.js](https://nodejs.org/):
|
||||
|
||||
```
|
||||
npm install ethers-wallet
|
||||
```
|
||||
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
|
||||
### Wallet API
|
||||
|
||||
An *Ethereum* wallet wraps a cryptographic private key, which is used to sign transactions and control the ether located at the wallet's address. These transactions can then be broadcast to the *Ethereum* network.
|
||||
|
||||
```javascript
|
||||
// A private key can be specified as a 32 byte buffer or hexidecimal string
|
||||
var privateKey = new Wallet.utils.Buffer([
|
||||
@@ -29,17 +56,36 @@ var privateKey = '0x314159265358979323846264338327950288419716939937510582097494
|
||||
// Create a wallet object
|
||||
var wallet = new Wallet(privateKey)
|
||||
|
||||
// Wallet privateKey
|
||||
console.log(wallet.privateKey)
|
||||
/// "0x3141592653589793238462643383279502884197169399375105820974944592"
|
||||
|
||||
// Wallet address
|
||||
console.log(wallet.address)
|
||||
/// "0x7357589f8e367c2C31F51242fB77B350A11830F3"
|
||||
|
||||
// Sign transactions
|
||||
wallet.sign({
|
||||
to: "0x06B5955A67D827CDF91823E3bB8F069e6c89c1D6",
|
||||
gasLimit: 3000000,
|
||||
gasPrice: "0x1000",
|
||||
value: "0x1000"
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Converting addresses
|
||||
|
||||
Addresses come in various forms, and it is often useful to convert between them. You can pass any valid address into any function, and the library will convert it internally as needed. The address types are:
|
||||
- **hexidecimal** - 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef (all letters must be either lower case or uppercase; no mixed case, as this implies a checksum address)
|
||||
- **ICAP** - XE49Q0EPSW7XTS5PRIE9226HRPOO69XRVU7 (uses the International Bank Account Number format)
|
||||
- **checksum** - 0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF (notice the case is adjusted encoding checkum information)
|
||||
|
||||
```javascript
|
||||
// ICAP Addresses
|
||||
Wallet.getIcapAddress(wallet.address)
|
||||
/// "XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V"
|
||||
|
||||
Wallet.getIcapAddress("XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V")
|
||||
/// "XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V"
|
||||
|
||||
// Get checksummed address (from ICAP)
|
||||
Wallet.getAddress("XE39DH16QOXYG5JY9BYY6JGZW8ORUPBX71V")
|
||||
/// "0x7357589f8e367c2C31F51242fB77B350A11830F3"
|
||||
@@ -50,27 +96,187 @@ Wallet.getAddress("0x7357589f8e367c2c31f51242fb77b350a11830f3")
|
||||
|
||||
// Detect address checksum errors (notice the last "f" should be lowercase)
|
||||
Wallet.getAddress('0x7357589f8e367c2c31f51242fb77b350a11830F3')
|
||||
/// Error: invalid checksum address
|
||||
/// throws Error: invalid checksum address
|
||||
```
|
||||
|
||||
// Sign transactions
|
||||
wallet.sign({
|
||||
to: "0x06B5955A67D827CDF91823E3bB8F069e6c89c1D6",
|
||||
gasLimit: 3000000,
|
||||
gasPrice: "0x1000",
|
||||
value: "0x1000"
|
||||
|
||||
### Crowdsale JSON Wallets
|
||||
|
||||
During the crowdsale, the Ethereum Project sold ether by generating *crowdsale JSON wallet*. These functions allow you to decrypt those files and retreive the private key.
|
||||
|
||||
```javascript
|
||||
|
||||
// See the test-wallets directory samples (the variable should be a string)
|
||||
var json = "... see the test-wallets directory for samples ...";
|
||||
|
||||
// Detect crowdsale JSON wallets
|
||||
Wallet.isCrowdsaleWallet(json)
|
||||
|
||||
// Get a wallet from a crowdsale JSON wallet
|
||||
var wallet = Wallet.decryptCrowdsaleWallet(json, password);
|
||||
console.log(wallet.address)
|
||||
console.log(wallet.privateKey)
|
||||
```
|
||||
|
||||
|
||||
### Secret Storage JSON Wallet
|
||||
|
||||
This API allows you to decrypt and encrypt the [Secret Storage](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) format used by *Geth* and many other wallet platforms (such as *ethers.io*).
|
||||
|
||||
The Secret Storage JSON Wallet format uses an algorithm called *scrypt*, which is intentionally CPU-intensive, which ensures that an attacker would need to tie up considerable resources to attempt to brute-force guess your password. It aslo means it may take some time (10-30 seconds) to decrypt or encrypt a wallet. So, these API calls use a callback to provide progress feedback as well as the opportunity to cancel the process.
|
||||
|
||||
The callback should look like `function(progress)` where progress is a Number between 0 and 1 (inclusive) and if the function returns `true`, then the process will be cancelled, calling the callback once more with `callback(new Error('cancelled'))`.
|
||||
|
||||
#### Wallet.decrypt(json, password[, callback]);
|
||||
|
||||
```javascript
|
||||
|
||||
// See the test-wallets directory samples (the variable should be a string)
|
||||
var json = "... see the test-wallets directory for samples ...";
|
||||
|
||||
// Decrypt a Secret Storage JSON wallet
|
||||
var shouldCancelDecrypt = false;
|
||||
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% decrypted');
|
||||
|
||||
// Optionally return true to stop this decryption; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelDecrypt;
|
||||
}
|
||||
|
||||
Wallet.decrypt(json, password, updateInterface).then(function(wallet) {
|
||||
// The wallet was successfully decrypted
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'invalid password') {
|
||||
// Wrong password
|
||||
|
||||
} else if (error.message === 'cancelled') {
|
||||
// The decryption was cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Wallet.prototype.encrypt(password[, options][, callback]);
|
||||
|
||||
```javascript
|
||||
|
||||
// Encrypt a wallet into a Secret Storage JSON Wallet (all options are optional)
|
||||
var bytes16 = '0xdeadbeef1deadbeef2deadbeef301234';
|
||||
var options = {
|
||||
salt: bytes16, // hex string or Buffer, any length
|
||||
iv: bytes16, // hex string or Buffer, 16 bytes
|
||||
uuid: bytes16, // hex string or Buffer, 16 bytes
|
||||
scrypt: {
|
||||
N: (1 << 17), // Number, power of 2 greater than 2
|
||||
p: 8, // Number
|
||||
r: 1 // Number
|
||||
}
|
||||
}
|
||||
|
||||
var wallet = new Wallet(privateKey);
|
||||
|
||||
var shouldCancelEncrypt = false;
|
||||
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% encrypted');
|
||||
|
||||
// Optionally return true to stop this encryption; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelEncrypt;
|
||||
}
|
||||
|
||||
wallet.encrypt(password, options, updateInterface).then(function(json) {
|
||||
// The wallet was successfully encrypted as a json string
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
### Brain Wallets
|
||||
|
||||
Brain wallets should not be considered a secure way to store large amounts of ether; anyone who knows your username/password can steal your funds.
|
||||
|
||||
```javascript
|
||||
|
||||
// Username and passwords must be buffers; see scrypt-js library for summary
|
||||
// of UTF-8 gotchas (@TOOD: include a link)
|
||||
var email = new Wallet.utils.Buffer('github@ricmoo.com', 'utf8');
|
||||
var password = new Wallet.utils.Buffer('password', 'utf8');
|
||||
|
||||
var shouldCancelSummon = false;
|
||||
|
||||
function updateInterface(progress) {
|
||||
console.log('The wallet is ' + parseInt(100 * progress) + '% generated');
|
||||
|
||||
// Optionally return true to stop this generation; this callback will get
|
||||
// called once more with callback(new Error("cancelled"))
|
||||
return shouldCancelSummon;
|
||||
}
|
||||
|
||||
Wallet.summonBrainWallet(email, password, updateInterface).then(function(wallet) {
|
||||
// The wallet was successfully generated
|
||||
|
||||
}, function(error) {
|
||||
if (error.message === 'cancelled') {
|
||||
// Cancelled
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Provider API
|
||||
------------
|
||||
|
||||
Connect to standard *Ethereum* nodes via RPC (if you have a local [parity](https://ethcore.io/parity.html) or [geth](https://github.com/ethereum/go-ethereum/wiki/Geth) instance running), or via [Etherscan](https://etherscan.io):
|
||||
|
||||
```javascript
|
||||
|
||||
// The Web3 library is NOT required, but if you have one (for example from Metamask)
|
||||
var web3Provider = new Web3.providers.HttpProvider('http://localhost:8545');
|
||||
var web3 = new Web3(web3Provider);
|
||||
|
||||
// All these are equivalent
|
||||
var wallet = new Wallet(privateKey, 'http://localhost:8545');
|
||||
var wallet = new Wallet(privateKey, web3Provider);
|
||||
var wallet = new Wallet(privateKey, web3);
|
||||
|
||||
// Or use Etherscan:
|
||||
var wallet = new Wallet(privateKey, new Wallet.providers.EtherscanProvider({testnet: true}));
|
||||
|
||||
|
||||
// With a provider attached, you can call additional methods on the wallet
|
||||
|
||||
// Get the wallet's balance
|
||||
wallet.getBalance().then(function(balance) {
|
||||
console.log(balance);
|
||||
});
|
||||
|
||||
// Get the current nonce for this wallet
|
||||
wallet.getTransactionCount().then(function(transactionCount) {
|
||||
console.log(transactionCount);
|
||||
})
|
||||
|
||||
// Send ether to another account or contract
|
||||
wallet.send(targetAddress, Wallet.parseEther('1.0')).then(function(txid) {
|
||||
console.log(txid);
|
||||
})
|
||||
```
|
||||
|
||||
Contract API
|
||||
------------
|
||||
|
||||
```javascript
|
||||
// Load a normal web3 object (you need a local RPC-enabled ethereum node running)
|
||||
var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
|
||||
|
||||
// Create your wallet
|
||||
var wallet = new Wallet('0x3141592653589793238462643383279502884197169399375105820974944592')
|
||||
var privateKey = '0x3141592653589793238462643383279502884197169399375105820974944592';
|
||||
|
||||
// Create your wallet with any method from the above Provider API
|
||||
var wallet = new Wallet(privateKey, 'http://localhost:8545')
|
||||
|
||||
console.log(wallet.address);
|
||||
/// "0x7357589f8e367c2C31F51242fB77B350A11830F3"
|
||||
@@ -103,7 +309,7 @@ var simpleStorageAbi = [
|
||||
];
|
||||
|
||||
// Get the contract
|
||||
var contract = wallet.getContract(web3, simpleStorageAddress, simpleStorageAbi)
|
||||
var contract = wallet.getContract(simpleStorageAddress, simpleStorageAbi)
|
||||
|
||||
// Set up events
|
||||
contract.onvaluechanged = function(oldValue, newValue) {
|
||||
@@ -122,7 +328,7 @@ contract.setValue("Hello World").then(function(txid) {
|
||||
console.log('txid: ' + txid);
|
||||
});
|
||||
|
||||
// Include ether with a state-changing call, or custom gasLimit or gasPrice
|
||||
// Include custom parameters with a state-changing call
|
||||
var options = {
|
||||
gasPrice: 1000 // in wei (default: from network)
|
||||
gasLimit: 3000000, // is gas (default: 3000000)
|
||||
@@ -132,16 +338,53 @@ contract.setValue("Hello World", options).then(function(txid) {
|
||||
console.log('txid: ' + txid);
|
||||
});
|
||||
|
||||
// Estimate the gas cost of calling a state-changing method (returns a BN.js)
|
||||
contract.estimate.setValue("Hello World").then(function(gasCost) {
|
||||
console.log(gasCost.toString(10));
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
A lot of the test cases are performed by comparing against known working implementations of many of the features of this library. To run the test suite, you must use `npm install` (without the `--production` flag, which would skip the development dependencies.)
|
||||
|
||||
To run the test suite,
|
||||
|
||||
```
|
||||
/Users/ethers> npm test
|
||||
|
||||
> ethers-wallet@0.0.9 test /Users/ethers/ethers-wallet
|
||||
> nodeunit test.js
|
||||
|
||||
Running test cases... (this can take a long time, please be patient)
|
||||
|
||||
index.js
|
||||
+ testPrivateKeyToAddress
|
||||
+ testChecksumAddress
|
||||
+ testIcapAddress
|
||||
+ testEtherFormat
|
||||
+ testTrasactions
|
||||
+ testSolidityCoder
|
||||
+ testContracts
|
||||
+ testSecretStorage
|
||||
+ testBrainWallet
|
||||
+ testContractAddress
|
||||
+ testProviders
|
||||
|
||||
OK: 52178 assertions (147788ms)
|
||||
```
|
||||
|
||||
There are also some test JSON wallets available in the [test-wallets](https://github.com/ethers-io/ethers-wallet/tree/master/test-wallets) directory.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
MIT Licensed, with the exceptions:
|
||||
- The Solidity encoder/decoder (LGPL)
|
||||
- RLP (MPL-2.0)
|
||||
|
||||
We are working on our own implementations so we can move off of them and have a completely MIT licensed implementation in the near future.
|
||||
We are working on our own implementations and will have the library 100% MIT in the near future.
|
||||
|
||||
Stay tuned!
|
||||
|
||||
22940
dist/ethers-wallet.js
vendored
22940
dist/ethers-wallet.js
vendored
File diff suppressed because it is too large
Load Diff
24
dist/ethers-wallet.min.js
vendored
24
dist/ethers-wallet.min.js
vendored
File diff suppressed because one or more lines are too long
284
examples/splitter/index.html
Normal file
284
examples/splitter/index.html
Normal file
@@ -0,0 +1,284 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Ethereum Classic Split Tool</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="centerer">
|
||||
<div class="centered">
|
||||
<h1>Split Ether Classic</h1>
|
||||
<hr />
|
||||
<h2>What does this tool do?</h2>
|
||||
<p>
|
||||
This tool will take a <i>geth</i> (or crowdsale) JSON wallet, decrypt it and
|
||||
send all its funds to <a href="http://etherscan.io/address/0x3474627d4f63a678266bc17171d87f8570936622#code">this contract</a>,
|
||||
which will:
|
||||
</p>
|
||||
<ul>
|
||||
<li>On the ETH branch — send the funds back to the original address</li>
|
||||
<li>On the ETC branch — send the funds to the provided target address (for example, a <a href="https://www.poloniex.com">Poloniex</a> deposit address)</li>
|
||||
</ul>
|
||||
<br />
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
I threw this together in couple of hours, mainly to split my own ether
|
||||
and test my <i>ethers-wallet</i> library (which is still missing features
|
||||
and is itself not ready for production use). Testing has been fairly minimal
|
||||
beyond trying it on a few wallets. <b>Use this at your own risk.</b>
|
||||
</p>
|
||||
<hr />
|
||||
<h2>Check Current ETC Balance</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>ETC Address:</th>
|
||||
<td><input type="text" id="checkAddress" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="submitCheck" class="submit disable">Check Classic Ether Balance</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>Split ETC/ETH</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>JSON Wallet:</th>
|
||||
<td><div class="file" id="drop">Drop JSON wallet file here</div><input type="file" id="json" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password:</th>
|
||||
<td><input type="password" id="password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Target ETC Address:</th>
|
||||
<td><input type="text" id="targetAddress" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="submitSplit" class="submit disable">Split Classic Ether</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="../../dist/ethers-wallet.js"></script>
|
||||
<script type="text/javascript">
|
||||
var submitCheck = document.getElementById('submitCheck');
|
||||
var submitSplit = document.getElementById('submitSplit');
|
||||
|
||||
var inputJson = document.getElementById('json');
|
||||
var inputCheckAddress = document.getElementById('checkAddress')
|
||||
var inputTargetAddress = document.getElementById('targetAddress')
|
||||
var inputPassword = document.getElementById('password');
|
||||
|
||||
var targetDrop = document.getElementById('drop');
|
||||
|
||||
var provider = new Wallet.providers.HttpProvider('https://linode-newark.ethers.ws:8002');
|
||||
|
||||
var contractAddress = '0x3474627d4f63a678266bc17171d87f8570936622';
|
||||
var contractAbi = JSON.parse('[{"constant":false,"inputs":[{"name":"balance","type":"uint256"}],"name":"claimDonations","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"isClassic","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"classicAddress","type":"address"}],"name":"split","outputs":[],"type":"function"},{"inputs":[],"type":"constructor"}]');
|
||||
|
||||
submitCheck.onclick = function() {
|
||||
if (submitCheck.classList.contains('disable')) { return; }
|
||||
|
||||
var address = document.getElementById('checkAddress').value;
|
||||
try {
|
||||
address = Wallet.getAddress(address);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
alert('invalid address');
|
||||
return;
|
||||
}
|
||||
provider.getBalance(address, 'latest').then(function(balance) {
|
||||
alert('Balance: ' + Wallet.formatEther(balance, {commify: true}) + ' ' + Wallet.etherSymbol);
|
||||
}, function(error) {
|
||||
if (error) {
|
||||
console.log(error);
|
||||
alert('Faied to get balance');
|
||||
}
|
||||
});
|
||||
|
||||
console.log('check', address);
|
||||
}
|
||||
|
||||
submitSplit.onclick = function() {
|
||||
if (submitSplit.classList.contains('disable')) { return; }
|
||||
|
||||
function done() {
|
||||
submitSplit.textContent = 'Split Classic Ether';
|
||||
inputJson.readOnly = false;
|
||||
inputPassword.readOnly = false;
|
||||
inputTargetAddress.readOnly = false;
|
||||
checkSplit();
|
||||
}
|
||||
|
||||
inputJson.readOnly = true;
|
||||
inputPassword.readOnly = true;
|
||||
inputTargetAddress.readOnly = true;
|
||||
|
||||
submitSplit.classList.add('disable');
|
||||
|
||||
var files = inputJson.files;
|
||||
if (files.length !== 1) {
|
||||
alert('No wallet found');
|
||||
return done();
|
||||
}
|
||||
|
||||
var password = new Wallet.utils.Buffer(inputPassword.value, 'utf8');
|
||||
|
||||
var targetAddress = document.getElementById('targetAddress').value;
|
||||
try {
|
||||
targetAddress = Wallet.getAddress(targetAddress);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
alert('invalid target address');
|
||||
return done();
|
||||
}
|
||||
|
||||
function processWallet(wallet) {
|
||||
console.log(wallet);
|
||||
if (wallet.address === targetAddress) {
|
||||
alert('Wallet address and target address cannot be the same.');
|
||||
return done();
|
||||
}
|
||||
|
||||
submitSplit.textContent = 'Decrypted \u2014 Processing (please wait)';
|
||||
|
||||
var contract = wallet.getContract(contractAddress, contractAbi);
|
||||
var data = contract.interface.split(targetAddress).data;
|
||||
|
||||
var transaction = {
|
||||
to: contractAddress,
|
||||
data: data
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
provider.getBalance(wallet.address, 'latest'),
|
||||
provider.getTransactionCount(wallet.address, 'latest'),
|
||||
provider.getGasPrice(),
|
||||
// provider.estimateGas(transaction)
|
||||
]).then(function(results) {
|
||||
var balance = results[0];
|
||||
var transactionCount = results[1];
|
||||
var gasPrice = results[2];
|
||||
var gasEstimate = new Wallet.utils.BN('29735');
|
||||
//results[3].add(new Wallet.utils.BN(21000));
|
||||
|
||||
transaction.gasPrice = '0x' + gasPrice.toString(16);
|
||||
transaction.nonce = transactionCount;
|
||||
transaction.gasLimit = gasEstimate;
|
||||
|
||||
var toSend = balance.sub(gasPrice.mul(gasEstimate));
|
||||
transaction.value = toSend;
|
||||
|
||||
console.log(transaction);
|
||||
|
||||
var accept = confirm('Balance: ' + Wallet.formatEther(balance, {commify: true}) + ' ' + Wallet.etherSymbol + '. Are you sure you want to split Classic Ether to ' + targetAddress + '?');
|
||||
if (accept) {
|
||||
var signedTransaction = wallet.sign(transaction);
|
||||
function showError(error) {
|
||||
console.log(error);
|
||||
done();
|
||||
alert('Error sending transaction');
|
||||
}
|
||||
provider.sendTransaction(signedTransaction).then(function(txid) {
|
||||
if (txid.match(/^0x0+$/)) {
|
||||
showError(new Error('txid was zero'));
|
||||
return;
|
||||
}
|
||||
done();
|
||||
console.log('txid:' + txid);
|
||||
alert('Success \u2014 ' + txid);
|
||||
}, function(error) {
|
||||
showError(error);
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
}, function(error) {
|
||||
done();
|
||||
console.log(error);
|
||||
alert('Error: Faied to fetch info');
|
||||
});
|
||||
}
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(e) {
|
||||
var json = e.target.result;
|
||||
if (Wallet.isCrowdsaleWallet(json)) {
|
||||
var wallet = Wallet.decryptCrowdsale(json, password);
|
||||
processWallet(wallet);
|
||||
} else if (Wallet.isValidWallet(json)) {
|
||||
Wallet.decrypt(json, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
done();
|
||||
console.log(error);
|
||||
if (error.message === 'invalid password') {
|
||||
alert('Wrong Password');
|
||||
} else {
|
||||
alert('Error Decrypting Wallet');
|
||||
}
|
||||
} else if (wallet) {
|
||||
processWallet(wallet);
|
||||
} else {
|
||||
submitSplit.textContent = 'Decrypting \u2014 ' + (parseInt(progress * 100) + '%');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
alert('unknown walet format');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
fileReader.readAsText(files[0]);
|
||||
}
|
||||
|
||||
inputCheckAddress.oninput = function() {
|
||||
try {
|
||||
Wallet.getAddress(inputCheckAddress.value);
|
||||
submitCheck.classList.remove('disable');
|
||||
} catch (error) {
|
||||
submitCheck.classList.add('disable');
|
||||
}
|
||||
}
|
||||
|
||||
function checkSplit() {
|
||||
if (!inputJson.files || inputJson.files.length !== 1) {
|
||||
submitSplit.classList.add('disable');
|
||||
return;
|
||||
}
|
||||
targetDrop.textContent = inputJson.files[0].name;
|
||||
|
||||
try {
|
||||
Wallet.getAddress(inputTargetAddress.value);
|
||||
} catch (error) {
|
||||
submitSplit.classList.add('disable');
|
||||
return;
|
||||
}
|
||||
|
||||
submitSplit.classList.remove('disable');
|
||||
}
|
||||
|
||||
inputTargetAddress.oninput = checkSplit;
|
||||
inputJson.onchange = checkSplit;
|
||||
|
||||
inputJson.addEventListener('dragover', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
targetDrop.classList.add('highlight');
|
||||
}, true);
|
||||
|
||||
inputJson.addEventListener('drop', function(event) {
|
||||
targetDrop.classList.remove('highlight');
|
||||
}, true);
|
||||
|
||||
|
||||
var check
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
187
examples/style.css
Normal file
187
examples/style.css
Normal file
@@ -0,0 +1,187 @@
|
||||
body {
|
||||
background-color: #eee;
|
||||
font-family: sans-serif;
|
||||
font-size: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.centerer {
|
||||
margin-left: 50%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.centered:before {
|
||||
box-shadow: -10px 0 15px -10px #999 inset;
|
||||
content: " ";
|
||||
height: 100%;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.centered:after {
|
||||
box-shadow: 10px 0 15px -10px #999 inset;
|
||||
content: " ";
|
||||
height: 100%;
|
||||
right: -10px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.centered {
|
||||
background-color: #fff;
|
||||
border-left: 1px solid #888;
|
||||
border-right: 1px solid #888;
|
||||
dbox-shadow: -1px 0 15px 0 #999;
|
||||
margin-left: -370px;
|
||||
min-height: 100%;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 0 15px 15px 0;
|
||||
}
|
||||
td {
|
||||
padding: 0 15px 15px 0;
|
||||
}
|
||||
|
||||
input[type=text] {
|
||||
border: 1px solid #555;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
input[type=password] {
|
||||
border: 1px solid #555;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
input[type=file] {
|
||||
dbackground: #fff;
|
||||
border: 1px solid #555;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
dvisibility: hidden;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
input[type=text].readonly {
|
||||
border: 1px solid #ccc;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.file {
|
||||
border: 1px solid green;
|
||||
color: #444;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin-top: 2px;
|
||||
padding: 13px 10px 12px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 478px;
|
||||
}
|
||||
|
||||
.file.highlight {
|
||||
box-shadow: 0px 0px 5px #888;
|
||||
}
|
||||
|
||||
.option {
|
||||
border: 1px solid #999;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
opacity: 0.3;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
transition: opacity 0.1s linear;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.option.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
box-shadow: 0px 0px 5px #888;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
div.activity {
|
||||
font-family: monospace;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
div.activity a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.username {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.submit {
|
||||
border: 1px solid #555;
|
||||
box-shadow: 0px 0px 5px #888;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
transition: opacity 0.1s linear;
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
.submit:hover {
|
||||
border: 1px solid #999;
|
||||
box-shadow: 0px 0px 5px #aaa;
|
||||
}
|
||||
|
||||
.submit:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.submit.disable {
|
||||
box-shadow: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.submit.disable:hover {
|
||||
border: 1px solid #555;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.submit.disable:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
500
examples/wallet/index.html
Normal file
500
examples/wallet/index.html
Normal file
@@ -0,0 +1,500 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Ethereum Wallet</title>
|
||||
<link rel="stylesheet" type="text/css" href="../style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="centerer" id="screen-select">
|
||||
<div class="centered">
|
||||
<h1>Ethereum Wallet Tool</h1>
|
||||
<hr />
|
||||
<h2>Summon Brain Wallet</h2>
|
||||
<p>
|
||||
A brain wallet generates an <i>Ethereum</i> wallet from a username and a password
|
||||
without using any servers to store your information. If you lose your username
|
||||
or password, <b>no one</b> can help you recover them. Anyone who can guess your
|
||||
username and password can steal your funds. Brain wallets should <b>not</b> be
|
||||
considered a safe way to store large amounts of ether nor for long periods of time.
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>E-mail Address:</th>
|
||||
<td><input type="text" placeholder="(e-mail address)" id="select-brainwallet-username" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password:</th>
|
||||
<td><input type="password" placeholder="(password)" id="select-brainwallet-password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Confirm Password:</th>
|
||||
<td><input type="password" placeholder="(same password)" id="select-brainwallet-confirm-password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="select-submit-brainwallet" class="submit disable">Summon Brain Wallet</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr />
|
||||
<h2>Load JSON Wallet</h2>
|
||||
<p>
|
||||
If you have a JSON wallet file from <i>geth</i> or from the initial <i>Ethereum</i>
|
||||
crowd sale, you can decrypt it here. No information is shared with <b>any</b>
|
||||
server.
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>JSON Wallet:</th>
|
||||
<td><div class="file" id="select-wallet-drop">Drop JSON wallet file here</div><input type="file" id="select-wallet-file" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password:</th>
|
||||
<td><input type="password" placeholder="(password)" id="select-wallet-password" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="select-submit-wallet" class="submit disable">Unlock JSON Wallet</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
<h2>Raw Private Key</h2>
|
||||
<p>
|
||||
</p>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Private Key:</th>
|
||||
<td><input type="text" placeholder="(private key)" id="select-privatekey" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="select-submit-privatekey" class="submit disable">Unlock JSON Wallet</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="centerer hidden" id="screen-loading">
|
||||
<div class="centered">
|
||||
<h1>Loading Wallet</h1>
|
||||
<hr />
|
||||
<h2 id="loading-header"></h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Progress:</th>
|
||||
<td>
|
||||
<input type="text" readonly="readonly" class="readonly" id="loading-status" value="" /></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="loading-cancel" class="submit">Cancel</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr />
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="centerer hidden" id="screen-wallet">
|
||||
<div class="centered">
|
||||
<h1>Ethereum Wallet<span id="wallet-username" class="username right"></span></h1>
|
||||
<hr />
|
||||
<h3>Wallet Details:</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Address:</th>
|
||||
<td>
|
||||
<input type="text" readonly="readonly" class="readonly" id="wallet-address" value="" /></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Network:</th>
|
||||
<td>
|
||||
<div class="option left" id="option-morden">Morden (testnet)</div>
|
||||
<div class="option right selected" id="option-homestead">Homestead (mainnet)</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Balance:</th>
|
||||
<td>
|
||||
<input type="text" readonly="readonly" class="readonly" id="wallet-balance" value="0.0" /></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Nonce:</th>
|
||||
<td>
|
||||
<input type="text" readonly="readonly" class="readonly" id="wallet-transaction-count" value="0" /></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="wallet-submit-refresh" class="submit">Refresh</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3>Send Ether:</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Target Address:</th>
|
||||
<td><input type="text" placeholder="(target address)" id="wallet-send-target-address" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Amount:</th>
|
||||
<td><input type="text" placeholder="(amount)" id="wallet-send-amount" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td>
|
||||
<div id="wallet-submit-send" class="submit disable">Send Ether</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3>Session Activity</h3>
|
||||
<div id="wallet-activity" class="activity"></div>
|
||||
|
||||
<hr />
|
||||
<h3>Disclaimer:</h3>
|
||||
<p>
|
||||
This is beta software, with no warranty. <b>Use at your own risk.</b>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="../../dist/ethers-wallet.js"></script>
|
||||
<script type="text/javascript">
|
||||
function setEnter(source, target) {
|
||||
source.onkeyup = function(e) {
|
||||
if (e.which === 13) { target.click(); }
|
||||
}
|
||||
}
|
||||
|
||||
var cancelScrypt = false;
|
||||
document.getElementById('loading-cancel').onclick = function() {
|
||||
cancelScrypt = true;
|
||||
};
|
||||
|
||||
(function() {
|
||||
var inputUsername = document.getElementById('select-brainwallet-username');
|
||||
var inputPassword = document.getElementById('select-brainwallet-password');
|
||||
var inputConfirmPassword = document.getElementById('select-brainwallet-confirm-password');
|
||||
var submit = document.getElementById('select-submit-brainwallet');
|
||||
|
||||
function checkBrainwallet() {
|
||||
if (inputUsername.value && inputPassword.value && inputPassword.value === inputConfirmPassword.value) {
|
||||
submit.classList.remove('disable');
|
||||
} else {
|
||||
submit.classList.add('disable');
|
||||
}
|
||||
}
|
||||
inputUsername.oninput = checkBrainwallet;
|
||||
inputPassword.oninput = checkBrainwallet;
|
||||
inputConfirmPassword.oninput = checkBrainwallet;
|
||||
|
||||
setEnter(inputUsername, submit);
|
||||
setEnter(inputPassword, submit);
|
||||
setEnter(inputConfirmPassword, submit);
|
||||
|
||||
submit.onclick = function() {
|
||||
if (submit.classList.contains('disable')) { return; }
|
||||
|
||||
var username = new Wallet.utils.Buffer(inputUsername.value, 'utf8');
|
||||
var password = new Wallet.utils.Buffer(inputPassword.value, 'utf8');
|
||||
|
||||
showLoading('Summoning Brain Wallet...');
|
||||
|
||||
cancelScrypt = false;
|
||||
|
||||
Wallet.summonBrainWallet(username, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
if (error.message !== 'cancelled') {
|
||||
alert('Unknown error');
|
||||
}
|
||||
showSelect();
|
||||
|
||||
} else if (wallet) {
|
||||
showWallet(wallet);
|
||||
document.getElementById('wallet-username').textContent = inputUsername.value;
|
||||
|
||||
} else {
|
||||
updateLoading(progress);
|
||||
}
|
||||
|
||||
return cancelScrypt;
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
(function() {
|
||||
var inputFile = document.getElementById('select-wallet-file');
|
||||
var targetDrop = document.getElementById('select-wallet-drop');
|
||||
var inputPassword = document.getElementById('select-wallet-password');
|
||||
var submit = document.getElementById('select-submit-wallet');
|
||||
|
||||
function check() {
|
||||
if (inputFile.files && inputFile.files.length === 1) {
|
||||
submit.classList.remove('disable');
|
||||
targetDrop.textContent = inputFile.files[0].name;
|
||||
} else {
|
||||
submit.classList.add('disable');
|
||||
}
|
||||
}
|
||||
inputFile.onchange = check;
|
||||
inputPassword.oninput = check;
|
||||
|
||||
setEnter(inputPassword, submit);
|
||||
|
||||
inputFile.addEventListener('dragover', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
targetDrop.classList.add('highlight');
|
||||
}, true);
|
||||
|
||||
inputFile.addEventListener('drop', function(event) {
|
||||
targetDrop.classList.remove('highlight');
|
||||
}, true);
|
||||
|
||||
submit.onclick = function() {
|
||||
if (submit.classList.contains('disable')) { return; }
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(e) {
|
||||
var json = e.target.result;
|
||||
|
||||
if (Wallet.isCrowdsaleWallet(json)) {
|
||||
showWallet(Wallet.decryptCrowdsale(json, password));
|
||||
|
||||
} else if (Wallet.isValidWallet(json)) {
|
||||
showLoading('Decrypting Wallet...');
|
||||
var password = new Wallet.utils.Buffer(inputPassword.value);
|
||||
|
||||
cancelScrypt = false;
|
||||
|
||||
Wallet.decrypt(json, password, function(error, wallet, progress) {
|
||||
if (error) {
|
||||
if (error.message === 'invalid password') {
|
||||
alert('Wrong Password');
|
||||
} else {
|
||||
console.log(error);
|
||||
alert('Error Decrypting Wallet');
|
||||
}
|
||||
showSelect();
|
||||
|
||||
} else if (wallet) {
|
||||
showWallet(wallet);
|
||||
|
||||
} else {
|
||||
updateLoading(progress);
|
||||
}
|
||||
|
||||
return cancelScrypt;
|
||||
});
|
||||
} else {
|
||||
alert('Unknown JSON wallet format');
|
||||
}
|
||||
};
|
||||
fileReader.readAsText(inputFile.files[0]);
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
(function() {
|
||||
var inputPrivatekey = document.getElementById('select-privatekey');
|
||||
var submit = document.getElementById('select-submit-privatekey');
|
||||
|
||||
function check() {
|
||||
if (inputPrivatekey.value.match(/^(0x)?[0-9A-fa-f]{64}$/)) {
|
||||
submit.classList.remove('disable');
|
||||
} else {
|
||||
submit.classList.add('disable');
|
||||
}
|
||||
}
|
||||
inputPrivatekey.oninput = check;
|
||||
|
||||
setEnter(inputPrivatekey, submit);
|
||||
|
||||
submit.onclick = function() {
|
||||
if (submit.classList.contains('disable')) { return; }
|
||||
var privateKey = inputPrivatekey.value;
|
||||
if (privateKey.substring(0, 2) !== '0x') { privateKey = '0x' + privateKey; }
|
||||
showWallet(new Wallet(privateKey));
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
var activeWallet = null;
|
||||
|
||||
function showError(error) {
|
||||
alert('Error \u2014 ' + error.message);
|
||||
}
|
||||
|
||||
// Refresh balance and transaction count in the UI
|
||||
var refresh = (function() {
|
||||
var inputBalance = document.getElementById('wallet-balance');
|
||||
var inputTransactionCount = document.getElementById('wallet-transaction-count');
|
||||
var submit = document.getElementById('wallet-submit-refresh');
|
||||
|
||||
function refresh() {
|
||||
addActivity('> Refreshing details...');
|
||||
activeWallet.getBalance('pending').then(function(balance) {
|
||||
addActivity('< Balance: ' + balance.toString(10));
|
||||
inputBalance.value = Wallet.formatEther(balance, {commify: true});
|
||||
}, function(error) {
|
||||
showError(error);
|
||||
});
|
||||
activeWallet.getTransactionCount('pending').then(function(transactionCount) {
|
||||
addActivity('< TransactionCount: ' + transactionCount);
|
||||
inputTransactionCount.value = transactionCount;
|
||||
}, function(error) {
|
||||
showError(error);
|
||||
});
|
||||
}
|
||||
submit.onclick = refresh;
|
||||
|
||||
return refresh;
|
||||
})();
|
||||
|
||||
var addActivity = (function() {
|
||||
var activity = document.getElementById('wallet-activity');
|
||||
return function(message, url) {
|
||||
var line = document.createElement('a');
|
||||
line.textContent = message;
|
||||
if (url) { line.setAttribute('href', url); }
|
||||
activity.appendChild(line);
|
||||
}
|
||||
})();
|
||||
|
||||
// Set up the wallet page
|
||||
(function() {
|
||||
|
||||
var inputTargetAddress = document.getElementById('wallet-send-target-address');
|
||||
var inputAmount = document.getElementById('wallet-send-amount');
|
||||
var submit = document.getElementById('wallet-submit-send');
|
||||
|
||||
// Validate the address and value (to enable the send button)
|
||||
function check() {
|
||||
try {
|
||||
Wallet.getAddress(inputTargetAddress.value);
|
||||
Wallet.parseEther(inputAmount.value);
|
||||
} catch (error) {
|
||||
submit.classList.add('disable');
|
||||
return;
|
||||
}
|
||||
submit.classList.remove('disable');
|
||||
}
|
||||
inputTargetAddress.oninput = check;
|
||||
inputAmount.oninput = check;
|
||||
|
||||
var optionMorden = document.getElementById('option-morden');
|
||||
var optionHomestead = document.getElementById('option-homestead');
|
||||
|
||||
// Select the morden network
|
||||
optionMorden.onclick = function() {
|
||||
if (optionMorden.classList.contains('selected')) { return; }
|
||||
addActivity('! Switched network: Morden');
|
||||
activeWallet.provider = new Wallet.providers.EtherscanProvider({testnet: true});
|
||||
optionMorden.classList.add('selected');
|
||||
optionHomestead.classList.remove('selected');
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Select the homestead network
|
||||
optionHomestead.onclick = function() {
|
||||
if (optionHomestead.classList.contains('selected')) { return; }
|
||||
addActivity('! Switched network: Homestead');
|
||||
activeWallet.provider = new Wallet.providers.EtherscanProvider({testnet: false});
|
||||
optionMorden.classList.remove('selected');
|
||||
optionHomestead.classList.add('selected');
|
||||
refresh();
|
||||
}
|
||||
|
||||
// Send ether
|
||||
submit.onclick = function() {
|
||||
|
||||
// Matt (from Etherscan) is working on a gasPrice API call, which
|
||||
// should be done within a week or so.
|
||||
var gasPrice = (activeWallet.provider.testnet ? 0x4a817c800: 0xba43b7400);
|
||||
console.log('GasPrice: ' + gasPrice);
|
||||
|
||||
var targetAddress = Wallet.getAddress(inputTargetAddress.value);
|
||||
var amountWei = Wallet.parseEther(inputAmount.value);
|
||||
activeWallet.send(targetAddress, amountWei, {
|
||||
gasPrice: gasPrice,
|
||||
gasLimit: 21000,
|
||||
}).then(function(txid) {
|
||||
var url = (activeWallet.provider.testnet ? 'https://testnet.etherscan.io/tx/': 'https://etherscan.io/tx/') + txid;
|
||||
addActivity('< Transaction sent: ' + txid.substring(0, 20) + '...', url);
|
||||
alert('Success!');
|
||||
|
||||
inputTargetAddress.value = '';
|
||||
inputAmount.value = '';
|
||||
submit.classList.add('disable');
|
||||
|
||||
refresh();
|
||||
}, function(error) {
|
||||
showError(error);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
function showSelect() {
|
||||
document.getElementById('screen-select').style.display = 'block';
|
||||
document.getElementById('screen-loading').style.display = 'none';
|
||||
document.getElementById('screen-wallet').style.display = 'none';
|
||||
}
|
||||
|
||||
function showLoading(title) {
|
||||
document.getElementById('screen-select').style.display = 'none';
|
||||
document.getElementById('screen-loading').style.display = 'block';
|
||||
document.getElementById('screen-wallet').style.display = 'none';
|
||||
|
||||
document.getElementById('loading-header').textContent = title;
|
||||
}
|
||||
|
||||
var loadingStatus = document.getElementById('loading-status');
|
||||
function updateLoading(progress) {
|
||||
loadingStatus.value = (parseInt(progress * 100)) + '%';
|
||||
}
|
||||
|
||||
function showWallet(wallet) {
|
||||
var testnet = document.getElementById('option-morden').classList.contains('selected');
|
||||
activeWallet = wallet;
|
||||
activeWallet.provider = new Wallet.providers.EtherscanProvider({testnet: testnet});
|
||||
|
||||
document.getElementById('screen-select').style.display = 'none';
|
||||
document.getElementById('screen-loading').style.display = 'none';
|
||||
document.getElementById('screen-wallet').style.display = 'block';
|
||||
|
||||
var inputWalletAddress = document.getElementById('wallet-address');
|
||||
inputWalletAddress.value = wallet.address;
|
||||
inputWalletAddress.onclick = function() {
|
||||
this.select();
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
//var privateKey = '0x3141592653589793238462643383279502884197169399375105820974944592';
|
||||
//showWallet(new Wallet(privateKey));
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
476
index.js
476
index.js
@@ -1,408 +1,96 @@
|
||||
var aes = require('aes-js');
|
||||
var elliptic = require('elliptic');
|
||||
//var elliptic = require('./elliptic/lib/elliptic.js');
|
||||
var pbkdf2 = require('pbkdf2');
|
||||
var rlp = require('rlp');
|
||||
'use strict';
|
||||
|
||||
var scrypt = require('scrypt-js');
|
||||
var uuid = require('uuid');
|
||||
|
||||
var Contract = require('./lib/contract.js');
|
||||
var providers = require('./lib/providers.js');
|
||||
var secretStorage = require('./lib/secret-storage.js');
|
||||
var Randomish = require('./lib/randomish.js');
|
||||
var SigningKey = require('./lib/signing-key.js');
|
||||
var Wallet = require('./lib/wallet.js');
|
||||
var units = require('./lib/units.js');
|
||||
|
||||
var utils = require('./lib/utils.js');
|
||||
var BN = utils.BN;
|
||||
|
||||
var secp256k1 = new (elliptic.ec)('secp256k1');
|
||||
|
||||
/*
|
||||
// @TODO: Make our own implementation of rlp; the existing one is MLP-2.0, we want MIT. :)
|
||||
function rlpEncodeLength(length) {
|
||||
}
|
||||
|
||||
function rlpArray(items) {
|
||||
var output = new Buffer(0);
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
function defineProperty(object, name, value) {
|
||||
Object.defineProperty(object, name, {
|
||||
enumerable: true,
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
var exportUtils = {};
|
||||
utils.defineProperty(Wallet, 'utils', exportUtils);
|
||||
|
||||
// These may go away in the future...
|
||||
defineProperty(exportUtils, '_aes', aes);
|
||||
defineProperty(exportUtils, '_scrypt', scrypt);
|
||||
utils.defineProperty(exportUtils, 'BN', BN);
|
||||
utils.defineProperty(exportUtils, 'Buffer', Buffer);
|
||||
|
||||
defineProperty(exportUtils, 'BN', BN);
|
||||
defineProperty(exportUtils, 'Buffer', Buffer);
|
||||
defineProperty(exportUtils, 'sha3', utils.sha3);
|
||||
utils.defineProperty(exportUtils, 'sha3', utils.sha3);
|
||||
utils.defineProperty(exportUtils, 'sha256', utils.sha256);
|
||||
|
||||
function getChecksumAddress(address) {
|
||||
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
address = address.substring(2).toLowerCase();
|
||||
var hash = utils.sha3(address);
|
||||
|
||||
address = address.split('');
|
||||
for (var i = 0; i < 40; i += 2) {
|
||||
if ((hash[i >> 1] >> 4) >= 8) {
|
||||
address[i] = address[i].toUpperCase();
|
||||
}
|
||||
if ((hash[i >> 1] & 0x0f) >= 8) {
|
||||
address[i + 1] = address[i + 1].toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
return '0x' + address.join('');
|
||||
}
|
||||
|
||||
// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number
|
||||
var ibanChecksum = (function() {
|
||||
|
||||
// Create lookup table
|
||||
var ibanLookup = {};
|
||||
for (var i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
|
||||
for (var i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
|
||||
|
||||
// How many decimal digits can we process? (for 64-bit float, this is 15)
|
||||
var safeDigits = Math.floor(Math.log10(Number.MAX_SAFE_INTEGER));
|
||||
|
||||
return function(address) {
|
||||
address = address.toUpperCase();
|
||||
address = address.substring(4) + address.substring(0, 2) + '00';
|
||||
|
||||
var expanded = address.split('');
|
||||
for (var i = 0; i < expanded.length; i++) {
|
||||
expanded[i] = ibanLookup[expanded[i]];
|
||||
}
|
||||
expanded = expanded.join('');
|
||||
|
||||
// Javascript can handle integers safely up to 15 (decimal) digits
|
||||
while (expanded.length >= safeDigits){
|
||||
var block = expanded.substring(0, safeDigits);
|
||||
expanded = parseInt(block, 10) % 97 + expanded.substring(block.length);
|
||||
}
|
||||
|
||||
checksum = String(98 - (parseInt(expanded, 10) % 97));
|
||||
while (checksum.length < 2) { checksum = '0' + checksum; }
|
||||
|
||||
return checksum;
|
||||
};
|
||||
})();
|
||||
|
||||
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
|
||||
defineProperty(exportUtils, 'getContractAddress', function(transaction) {
|
||||
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
|
||||
throw new Error('invalid from');
|
||||
}
|
||||
|
||||
return '0x' + utils.sha3(rlp.encode([
|
||||
utils.hexOrBuffer(transaction.from),
|
||||
utils.hexOrBuffer(utils.hexlify(transaction.nonce, 'nonce'))
|
||||
])).slice(12).toString('hex');
|
||||
});
|
||||
|
||||
var transactionFields = [
|
||||
{name: 'nonce', maxLength: 32, },
|
||||
{name: 'gasPrice', maxLength: 32, },
|
||||
{name: 'gasLimit', maxLength: 32, },
|
||||
{name: 'to', length: 20, },
|
||||
{name: 'value', maxLength: 32, },
|
||||
{name: 'data'},
|
||||
];
|
||||
|
||||
|
||||
function Wallet(privateKey) {
|
||||
if (!(this instanceof Wallet)) { throw new Error('missing new'); }
|
||||
|
||||
if (typeof(privateKey) === 'string' && privateKey.match(/^0x[0-9A-Fa-f]{64}$/)) {
|
||||
privateKey = new Buffer(privateKey.substring(2), 'hex');
|
||||
} else if (!Buffer.isBuffer(privateKey) || privateKey.length !== 32) {
|
||||
throw new Error('invalid private key');
|
||||
}
|
||||
|
||||
var keyPair = secp256k1.keyFromPrivate(privateKey);
|
||||
|
||||
var publicKey = (new Buffer(keyPair.getPublic(false, 'hex'), 'hex')).slice(1);
|
||||
var address = Wallet.getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
defineProperty(this, 'address', address);
|
||||
|
||||
defineProperty(this, 'sign', function(transaction) {
|
||||
var raw = [];
|
||||
transactionFields.forEach(function(fieldInfo) {
|
||||
var value = transaction[fieldInfo.name] || (new Buffer(0));
|
||||
value = utils.hexOrBuffer(utils.hexlify(value), fieldInfo.name);
|
||||
|
||||
// Fixed-width field
|
||||
if (fieldInfo.length && value.length !== fieldInfo.length) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'wrong length';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Variable-width (with a maximum)
|
||||
if (fieldInfo.maxLength) {
|
||||
value = utils.stripZeros(value);
|
||||
if (value.length > fieldInfo.maxLength) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'too long';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
raw.push(value);
|
||||
});
|
||||
|
||||
var digest = utils.sha3(rlp.encode(raw));
|
||||
|
||||
var signature = keyPair.sign(digest, {canonical: true});
|
||||
var s = signature.s;
|
||||
var v = signature.recoveryParam;
|
||||
|
||||
raw.push(new Buffer([27 + v]));
|
||||
raw.push(utils.bnToBuffer(signature.r));
|
||||
raw.push(utils.bnToBuffer(s));
|
||||
|
||||
return ('0x' + rlp.encode(raw).toString('hex'));
|
||||
});
|
||||
}
|
||||
|
||||
defineProperty(Wallet, 'utils', exportUtils);
|
||||
|
||||
defineProperty(Wallet, 'getAddress', function(address) {
|
||||
var result = null;
|
||||
|
||||
if (typeof(address) !== 'string') { throw new Error('invalid address'); }
|
||||
|
||||
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
|
||||
|
||||
// Missing the 0x prefix
|
||||
if (address.substring(0, 2) !== '0x') { address = '0x' + address; }
|
||||
|
||||
result = getChecksumAddress(address);
|
||||
|
||||
// It is a checksummed address with a bad checksum
|
||||
if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
|
||||
throw new Error('invalid address checksum');
|
||||
}
|
||||
|
||||
// Maybe ICAP? (we only support direct mode)
|
||||
} else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
|
||||
|
||||
// It is an ICAP address with a bad checksum
|
||||
if (address.substring(2, 4) !== ibanChecksum(address)) {
|
||||
throw new Error('invalid address icap checksum');
|
||||
}
|
||||
|
||||
result = (new BN(address.substring(4), 36)).toString(16);
|
||||
while (result.length < 40) { result = '0' + result; }
|
||||
result = getChecksumAddress('0x' + result);
|
||||
|
||||
} else {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
defineProperty(Wallet, 'getIcapAddress', function(address) {
|
||||
address = Wallet.getAddress(address).substring(2);
|
||||
var base36 = (new BN(address, 16)).toString(36).toUpperCase();
|
||||
while (base36.length < 30) { base36 = '0' + base36; }
|
||||
return 'XE' + ibanChecksum('XE00' + base36) + base36;
|
||||
});
|
||||
|
||||
defineProperty(Wallet, '_Contract', Contract);
|
||||
|
||||
var allowedTransactionKeys = {
|
||||
data: true, from: true, gasLimit: true, gasPrice:true, to: true, value: true
|
||||
}
|
||||
function AttachedContract(web3, wallet, contractAddress, contract) {
|
||||
utils.defineProperty(this, 'web3', web3);
|
||||
utils.defineProperty(this, 'wallet', wallet);
|
||||
utils.defineProperty(this, 'contractAddress', contractAddress);
|
||||
|
||||
utils.defineProperty(this, '_contract', contract);
|
||||
|
||||
function getWeb3Promise(method) {
|
||||
var params = Array.prototype.slice.call(arguments, 1);
|
||||
return new Promise(function(resolve, reject) {
|
||||
params.push(function(error, result) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(result);
|
||||
});
|
||||
web3.eth[method].apply(web3, params);
|
||||
});
|
||||
}
|
||||
|
||||
var self = this;
|
||||
|
||||
var filters = {};
|
||||
function setupFilter(call, callback) {
|
||||
var info = filters[call.name];
|
||||
|
||||
// Stop and remove the filter
|
||||
if (!callback) {
|
||||
if (info) { info.filter.stopWatching(); }
|
||||
delete filters[call.name];
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(callback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
// Already have a filter, just update the callback
|
||||
if (info) {
|
||||
info.callback = callback;
|
||||
return;
|
||||
}
|
||||
|
||||
info = {callback: callback};
|
||||
filters[call.name] = info;
|
||||
|
||||
// Start a new filter
|
||||
info.filter = web3.eth.filter({
|
||||
address: contractAddress,
|
||||
topics: call.topics
|
||||
}, function(error, result) {
|
||||
// @TODO: Emit errors to .onerror? Maybe?
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
info.callback.apply(self, call.parse(result.data));
|
||||
} catch(error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function runMethod(method) {
|
||||
return function() {
|
||||
var transaction = {}
|
||||
|
||||
var params = Array.prototype.slice.call(arguments);
|
||||
if (params.length == contract[method].inputs.length + 1) {
|
||||
transaction = params.pop();
|
||||
if (typeof(transaction) !== 'object') {
|
||||
throw new Error('invalid transaction overrides');
|
||||
}
|
||||
for (var key in transaction) {
|
||||
if (!allowedTransactionKeys[key]) {
|
||||
throw new Error('unknown transaction override ' + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var call = contract[method].apply(contract, params);
|
||||
switch (call.type) {
|
||||
case 'call':
|
||||
['data', 'gasLimit', 'gasPrice', 'to', 'value'].forEach(function(key) {
|
||||
if (transaction[key] != null) {
|
||||
throw new Error('call cannot override ' + key) ;
|
||||
}
|
||||
});
|
||||
transaction.data = call.data;
|
||||
if (transaction.from == null) {
|
||||
transaction.from = wallet.address;
|
||||
}
|
||||
transaction.to = contractAddress;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
getWeb3Promise('call', transaction).then(function(value) {
|
||||
resolve(call.parse(value));
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
case 'transaction':
|
||||
['data', 'from', 'to'].forEach(function(key) {
|
||||
if (transaction[key] != null) {
|
||||
throw new Error('transaction cannot override ' + key) ;
|
||||
}
|
||||
});
|
||||
transaction.data = call.data;
|
||||
transaction.to = contractAddress;
|
||||
if (transaction.gasLimit == null) {
|
||||
transaction.gasLimit = 3000000;
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
Promise.all([
|
||||
getWeb3Promise('getTransactionCount', wallet.address, 'pending'),
|
||||
getWeb3Promise('getGasPrice'),
|
||||
]).then(function(results) {
|
||||
if (transaction.nonce == null) {
|
||||
transaction.nonce = results[0];
|
||||
} else if (console.warn) {
|
||||
console.warn('Overriding suggested nonce: ' + results[0]);
|
||||
}
|
||||
if (transaction.gasPrice == null) {
|
||||
transaction.gasPrice = results[1];
|
||||
} else if (console.warn) {
|
||||
console.warn('Overriding suggested gasPrice: ' + utils.hexlify(results[1]));
|
||||
}
|
||||
var signedTransaction = wallet.sign(transaction);
|
||||
/*
|
||||
data: call.data,
|
||||
gasLimit: 3000000,
|
||||
gasPrice: results[1],
|
||||
nonce: results[0],
|
||||
to: contractAddress,
|
||||
});
|
||||
*/
|
||||
getWeb3Promise('sendRawTransaction', signedTransaction).then(function(txid) {
|
||||
resolve(txid);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
contract.methods.forEach(function(method) {
|
||||
utils.defineProperty(this, method, runMethod(method));
|
||||
}, this);
|
||||
|
||||
contract.events.forEach(function(method) {
|
||||
var call = contract[method].apply(contract, []);
|
||||
Object.defineProperty(self, 'on' + call.name.toLowerCase(), {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
console.log('get');
|
||||
var info = filters[call.name];
|
||||
if (!info || !info[call.name]) { return null; }
|
||||
return info.callback;
|
||||
},
|
||||
set: function(value) {
|
||||
console.log('set');
|
||||
setupFilter(call, value);
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
}
|
||||
|
||||
defineProperty(Wallet.prototype, 'getContract', function(web3, address, abi) {
|
||||
return new AttachedContract(web3, this, address, new Contract(abi));
|
||||
});
|
||||
utils.defineProperty(exportUtils, 'getContractAddress', utils.getContractAddress);
|
||||
|
||||
module.exports = Wallet;
|
||||
|
||||
|
||||
utils.defineProperty(Wallet, 'etherSymbol', '\uD835\uDF63');
|
||||
|
||||
utils.defineProperty(Wallet, 'formatEther', units.formatEther);
|
||||
utils.defineProperty(Wallet, 'parseEther', units.parseEther);
|
||||
|
||||
utils.defineProperty(Wallet, 'getAddress', utils.getAddress);
|
||||
utils.defineProperty(Wallet, 'getIcapAddress', utils.getIcapAddress);
|
||||
|
||||
utils.defineProperty(Wallet, 'isCrowdsaleWallet', secretStorage.isCrowdsaleWallet);
|
||||
utils.defineProperty(Wallet, 'isValidWallet', secretStorage.isValidWallet);
|
||||
|
||||
utils.defineProperty(Wallet, 'decryptCrowdsale', function(json, password) {
|
||||
return new Wallet(secretStorage.decryptCrowdsale(json, password));
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, 'decrypt', function(json, password, progressCallback) {
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
secretStorage.decrypt(json, password, progressCallback).then(function(signingKey) {
|
||||
resolve(new Wallet(signingKey));
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, progressCallback) {
|
||||
if (typeof(options) === 'function' && !progressCallback) {
|
||||
progressCallback = options;
|
||||
options = {};
|
||||
}
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
return secretStorage.encrypt(this.privateKey, password, options, progressCallback);
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, 'summonBrainWallet', function(username, password, progressCallback) {
|
||||
if (progressCallback && typeof(progressCallback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
scrypt(password, username, (1 << 18), 8, 1, 32, function(error, progress, key) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (key) {
|
||||
resolve(new Wallet(new Buffer(key)));
|
||||
} else if (progressCallback) {
|
||||
progressCallback(progress);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
utils.defineProperty(Wallet, 'providers', providers);
|
||||
|
||||
|
||||
utils.defineProperty(Wallet, 'randomish', new Randomish());
|
||||
|
||||
module.exports = Wallet;
|
||||
|
||||
37
lib/browser-random-bytes.js
Normal file
37
lib/browser-random-bytes.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var crypto = global.crypto || global.msCrypto;
|
||||
if (!crypto || !crypto.getRandomValues) {
|
||||
console.log('WARNING: Missing strong random number source; using weak randomBytes');
|
||||
crypto = {
|
||||
getRandomValues: function(length) {
|
||||
|
||||
for (var i = 0; i < buffer.length; i++) {
|
||||
buffer[i] = parseInt(256 * Math.random());
|
||||
}
|
||||
|
||||
return buffer;
|
||||
},
|
||||
_weakCrypto: true
|
||||
};
|
||||
} else {
|
||||
console.log('Found strong random number source');
|
||||
}
|
||||
|
||||
function randomBytes(length) {
|
||||
if (length <= 0 || length > 1024 || parseInt(length) != length) {
|
||||
throw new Error('invalid length');
|
||||
}
|
||||
|
||||
var buffer = new Buffer(length);
|
||||
crypto.getRandomValues(buffer);
|
||||
return buffer;
|
||||
};
|
||||
|
||||
if (crypto._weakCrypto === true) {
|
||||
utils.defineProperty(randomBytes, '_weakCrypto', true);
|
||||
}
|
||||
|
||||
module.exports = randomBytes;
|
||||
8
lib/browser-xmlhttprequest.js
Normal file
8
lib/browser-xmlhttprequest.js
Normal file
@@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
module.exports.XMLHttpRequest = XMLHttpRequest;
|
||||
} catch(error) {
|
||||
console.log('Warning: XMLHttpRequest is not defined');
|
||||
module.exports.XMLHttpRequest = null;
|
||||
}
|
||||
553
lib/contract.js
553
lib/contract.js
@@ -1,8 +1,8 @@
|
||||
// "coder" is not MIT... We need to find/make an MIT compatible implementation
|
||||
var coder = require('../node_modules/web3/lib/solidity/coder.js');
|
||||
'use strict';
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
// Creates property that is immutable
|
||||
function defineFrozen(object, name, value) {
|
||||
var frozen = JSON.stringify(value);
|
||||
Object.defineProperty(object, name, {
|
||||
@@ -11,6 +11,7 @@ function defineFrozen(object, name, value) {
|
||||
});
|
||||
}
|
||||
|
||||
// getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3]
|
||||
function getKeys(params, key) {
|
||||
if (!Array.isArray(params)) { throw new Error('invalid params'); }
|
||||
|
||||
@@ -18,15 +19,292 @@ function getKeys(params, key) {
|
||||
|
||||
for (var i = 0; i < params.length; i++) {
|
||||
if (typeof(params[i][key]) !== 'string') { throw new Error('invalid abi'); }
|
||||
// @TODO: Check type are valid?
|
||||
result.push(params[i][key]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function Contract(abi) {
|
||||
if (!(this instanceof Contract)) { throw new Error('missing new'); }
|
||||
// Convert the value from a Number to a BN (if necessary)
|
||||
function numberOrBN(value) {
|
||||
if (!value.eq) {
|
||||
if (typeof(value) !== 'number') {
|
||||
throw new Error('invalid number');
|
||||
}
|
||||
value = new utils.BN(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function zpad(buffer, length) {
|
||||
var zero = new Buffer([0]);
|
||||
while (buffer.length < length) {
|
||||
buffer = Buffer.concat([zero, buffer]);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// There seems to be a but in maskn, so we are doing this for now.
|
||||
var bitmasks = [];
|
||||
(function() {
|
||||
var mask = '';
|
||||
for (var i = 0; i < 33; i++) {
|
||||
bitmasks.push(new utils.BN(mask, 16));
|
||||
mask += 'ff';
|
||||
}
|
||||
})();
|
||||
|
||||
function coderNumber(size, signed) {
|
||||
return {
|
||||
encode: function(value) {
|
||||
value = numberOrBN(value)
|
||||
value = value.toTwos(size * 8).and(bitmasks[size]);
|
||||
if (signed) {
|
||||
value = value.fromTwos(size * 8).toTwos(256);
|
||||
}
|
||||
return value.toArrayLike(Buffer, 'be', 32);
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
var junkLength = 32 - size;
|
||||
var value = new utils.BN(data.slice(offset + junkLength, offset + 32));
|
||||
if (signed) {
|
||||
value = value.fromTwos(size * 8);
|
||||
} else {
|
||||
value = value.and(bitmasks[size]);
|
||||
}
|
||||
return {
|
||||
consumed: 32,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
var uint256Coder = coderNumber(32, false);
|
||||
|
||||
var coderBoolean = {
|
||||
encode: function(value) {
|
||||
return uint256Coder.encode(value ? 1: 0);
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
var result = uint256Coder.decode(data, offset);
|
||||
return {
|
||||
consumed: result.consumed,
|
||||
value: !result.value.isZero()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function coderFixedBytes(length) {
|
||||
return {
|
||||
encode: function(value) {
|
||||
value = utils.hexOrBuffer(value);
|
||||
if (length === 32) { return value; }
|
||||
|
||||
var result = new Buffer(32);
|
||||
result.fill(0);
|
||||
value.copy(result);
|
||||
return result;
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
if (data.length < offset + 32) { throw new Error('invalid bytes' + length); }
|
||||
|
||||
return {
|
||||
consumed: 32,
|
||||
value: '0x' + data.slice(offset, offset + length).toString('hex')
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var coderAddress = {
|
||||
encode: function(value) {
|
||||
if (!utils.isHexString(value, 20)) { throw new Error('invalid address'); }
|
||||
value = utils.hexOrBuffer(value);
|
||||
var result = new Buffer(32);
|
||||
result.fill(0);
|
||||
value.copy(result, 12);
|
||||
return result;
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
if (data.length < offset + 32) { throw new Error('invalid address'); }
|
||||
return {
|
||||
consumed: 32,
|
||||
value: '0x' + data.slice(offset + 12, offset + 32).toString('hex')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _encodeDynamicBytes(value) {
|
||||
var dataLength = parseInt(32 * Math.ceil(value.length / 32));
|
||||
var padding = new Buffer(dataLength - value.length);
|
||||
padding.fill(0);
|
||||
|
||||
return Buffer.concat([
|
||||
uint256Coder.encode(value.length),
|
||||
value,
|
||||
padding
|
||||
]);
|
||||
}
|
||||
|
||||
function _decodeDynamicBytes(data, offset) {
|
||||
if (data.length < offset + 32) { throw new Error('invalid bytes'); }
|
||||
|
||||
var length = uint256Coder.decode(data, offset).value;
|
||||
length = length.toNumber();
|
||||
if (data.length < offset + 32 + length) { throw new Error('invalid bytes'); }
|
||||
|
||||
return {
|
||||
consumed: parseInt(32 + 32 * Math.ceil(length / 32)),
|
||||
value: data.slice(offset + 32, offset + 32 + length),
|
||||
}
|
||||
}
|
||||
|
||||
var coderDynamicBytes = {
|
||||
encode: function(value) {
|
||||
return _encodeDynamicBytes(utils.hexOrBuffer(value));
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
var result = _decodeDynamicBytes(data, offset);
|
||||
result.value = '0x' + result.value.toString('hex');
|
||||
return result;
|
||||
},
|
||||
dynamic: true
|
||||
};
|
||||
|
||||
var coderString = {
|
||||
encode: function(value) {
|
||||
return _encodeDynamicBytes(new Buffer(value, 'utf8'));
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
var result = _decodeDynamicBytes(data, offset);
|
||||
result.value = result.value.toString('utf8');
|
||||
return result;
|
||||
},
|
||||
dynamic: true
|
||||
};
|
||||
|
||||
function coderArray(coder, length) {
|
||||
return {
|
||||
encode: function(value) {
|
||||
if (!Array.isArray(value)) { throw new Error('invalid array'); }
|
||||
|
||||
var result = new Buffer(0);
|
||||
if (length === -1) {
|
||||
length = value.length;
|
||||
result = uint256Coder.encode(length);
|
||||
}
|
||||
|
||||
if (length !== value.length) { throw new Error('size mismatch'); }
|
||||
|
||||
value.forEach(function(value) {
|
||||
result = Buffer.concat([
|
||||
result,
|
||||
coder.encode(value)
|
||||
]);
|
||||
});
|
||||
|
||||
return result;
|
||||
},
|
||||
decode: function(data, offset) {
|
||||
// @TODO:
|
||||
//if (data.length < offset + length * 32) { throw new Error('invalid array'); }
|
||||
|
||||
var consumed = 0;
|
||||
|
||||
var result;
|
||||
if (length === -1) {
|
||||
result = uint256Coder.decode(data, offset);
|
||||
length = result.value.toNumber();
|
||||
consumed += result.consumed;
|
||||
offset += result.consumed;
|
||||
}
|
||||
|
||||
var value = [];
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var result = coder.decode(data, offset);
|
||||
consumed += result.consumed;
|
||||
offset += result.consumed;
|
||||
value.push(result.value);
|
||||
}
|
||||
|
||||
return {
|
||||
consumed: consumed,
|
||||
value: value,
|
||||
}
|
||||
},
|
||||
dynamic: (length === -1)
|
||||
}
|
||||
}
|
||||
|
||||
// Break the type up into [staticType][staticArray]*[dynamicArray]? | [dynamicType] and
|
||||
// build the coder up from its parts
|
||||
var paramTypePart = new RegExp(/^((u?int|bytes)([0-9]*)|(address|bool|string)|(\[([0-9]*)\]))/);
|
||||
function getParamCoder(type) {
|
||||
var coder = null;
|
||||
while (type) {
|
||||
var part = type.match(paramTypePart);
|
||||
if (!part) { throw new Error('invalid type: ' + type); }
|
||||
type = type.substring(part[0].length);
|
||||
|
||||
var prefix = (part[2] || part[4] || part[5]);
|
||||
switch (prefix) {
|
||||
case 'int': case 'uint':
|
||||
if (coder) { throw new Error('invalid type ' + type); }
|
||||
var size = parseInt(part[3] || 256);
|
||||
if (size === 0 || size > 256 || (size % 8) !== 0) {
|
||||
throw new Error('invalid type ' + type);
|
||||
}
|
||||
coder = coderNumber(size / 8, (prefix === 'int'));
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
if (coder) { throw new Error('invalid type ' + type); }
|
||||
coder = coderBoolean;
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
if (coder) { throw new Error('invalid type ' + type); }
|
||||
coder = coderString;
|
||||
break;
|
||||
|
||||
case 'bytes':
|
||||
if (coder) { throw new Error('invalid type ' + type); }
|
||||
if (part[3]) {
|
||||
var size = parseInt(part[3]);
|
||||
if (size === 0 || size > 32) {
|
||||
throw new Error('invalid type ' + type);
|
||||
}
|
||||
coder = coderFixedBytes(size);
|
||||
} else {
|
||||
coder = coderDynamicBytes;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'address':
|
||||
if (coder) { throw new Error('invalid type ' + type); }
|
||||
coder = coderAddress;
|
||||
break;
|
||||
|
||||
case '[]':
|
||||
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); }
|
||||
coder = coderArray(coder, -1);
|
||||
break;
|
||||
|
||||
// "[0-9+]"
|
||||
default:
|
||||
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); }
|
||||
var size = parseInt(part[6]);
|
||||
coder = coderArray(coder, size);
|
||||
}
|
||||
}
|
||||
|
||||
if (!coder) { throw new Error('invalid type'); }
|
||||
return coder;
|
||||
}
|
||||
|
||||
function Interface(abi) {
|
||||
if (!(this instanceof Interface)) { throw new Error('missing new'); }
|
||||
|
||||
//defineProperty(this, 'address', address);
|
||||
|
||||
@@ -36,6 +314,8 @@ function Contract(abi) {
|
||||
var methods = [], events = [];
|
||||
abi.forEach(function(method) {
|
||||
|
||||
var func = null;
|
||||
|
||||
switch (method.type) {
|
||||
case 'function':
|
||||
methods.push(method.name);
|
||||
@@ -60,14 +340,14 @@ function Contract(abi) {
|
||||
|
||||
signature = '0x' + utils.sha3(signature).slice(0, 4).toString('hex');
|
||||
|
||||
result.data = signature + coder.encodeParams(inputTypes, params);
|
||||
result.data = signature + Interface.encodeParams(inputTypes, params).substring(2);
|
||||
if (method.constant) {
|
||||
result.type = 'call';
|
||||
result.parse = function(data) {
|
||||
return coder.decodeParams(
|
||||
return Interface.decodeParams(
|
||||
outputTypes,
|
||||
utils.hexOrBuffer(data).toString('hex')
|
||||
)
|
||||
utils.hexOrBuffer(data)
|
||||
);
|
||||
};
|
||||
} else {
|
||||
result.type = 'transaction';
|
||||
@@ -97,9 +377,9 @@ function Contract(abi) {
|
||||
topics: ['0x' + utils.sha3(signature).toString('hex')],
|
||||
};
|
||||
result.parse = function(data) {
|
||||
return coder.decodeParams(
|
||||
return Interface.decodeParams(
|
||||
inputTypes,
|
||||
utils.hexOrBuffer(data).toString('hex')
|
||||
utils.hexOrBuffer(data)
|
||||
);
|
||||
};
|
||||
return result;
|
||||
@@ -117,6 +397,7 @@ function Contract(abi) {
|
||||
})();
|
||||
break;
|
||||
}
|
||||
|
||||
utils.defineProperty(this, method.name, func);
|
||||
}, this);
|
||||
|
||||
@@ -124,4 +405,254 @@ function Contract(abi) {
|
||||
defineFrozen(this, 'events', events);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
utils.defineProperty(Interface, 'encodeParams', function(types, values) {
|
||||
if (types.length !== values.length) { throw new Error('types/values mismatch'); }
|
||||
|
||||
var parts = [];
|
||||
|
||||
types.forEach(function(type, index) {
|
||||
var coder = getParamCoder(type);
|
||||
parts.push({dynamic: coder.dynamic, value: coder.encode(values[index])});
|
||||
})
|
||||
|
||||
function alignSize(size) {
|
||||
return parseInt(32 * Math.ceil(size / 32));
|
||||
}
|
||||
|
||||
var staticSize = 0, dynamicSize = 0;
|
||||
parts.forEach(function(part) {
|
||||
if (part.dynamic) {
|
||||
staticSize += 32;
|
||||
dynamicSize += alignSize(part.value.length);
|
||||
} else {
|
||||
staticSize += alignSize(part.value.length);
|
||||
}
|
||||
});
|
||||
|
||||
var offset = 0, dynamicOffset = staticSize;
|
||||
var data = new Buffer(staticSize + dynamicSize);
|
||||
|
||||
parts.forEach(function(part, index) {
|
||||
if (part.dynamic) {
|
||||
uint256Coder.encode(dynamicOffset).copy(data, offset);
|
||||
offset += 32;
|
||||
|
||||
part.value.copy(data, dynamicOffset);
|
||||
dynamicOffset += alignSize(part.value.length);
|
||||
} else {
|
||||
part.value.copy(data, offset);
|
||||
offset += alignSize(part.value.length);
|
||||
}
|
||||
});
|
||||
|
||||
return '0x' + data.toString('hex');
|
||||
});
|
||||
|
||||
utils.defineProperty(Interface, 'decodeParams', function(types, data) {
|
||||
data = utils.hexOrBuffer(data);
|
||||
|
||||
var values = [];
|
||||
var offset = 0;
|
||||
types.forEach(function(type) {
|
||||
var coder = getParamCoder(type);
|
||||
if (coder.dynamic) {
|
||||
var dynamicOffset = uint256Coder.decode(data, offset);
|
||||
var result = coder.decode(data, dynamicOffset.value.toNumber());
|
||||
offset += dynamicOffset.consumed;
|
||||
} else {
|
||||
var result = coder.decode(data, offset);
|
||||
offset += result.consumed;
|
||||
}
|
||||
values.push(result.value);
|
||||
});
|
||||
return values;
|
||||
});
|
||||
|
||||
|
||||
var allowedTransactionKeys = {
|
||||
data: true, from: true, gasLimit: true, gasPrice:true, to: true, value: true
|
||||
}
|
||||
|
||||
function Contract(wallet, contractAddress, contractInterface) {
|
||||
utils.defineProperty(this, 'wallet', wallet);
|
||||
|
||||
utils.defineProperty(this, 'contractAddress', contractAddress);
|
||||
utils.defineProperty(this, 'interface', contractInterface);
|
||||
|
||||
var self = this;
|
||||
|
||||
var filters = {};
|
||||
function setupFilter(call, callback) {
|
||||
var info = filters[call.name];
|
||||
|
||||
// Stop and remove the filter
|
||||
if (!callback) {
|
||||
if (info) { info.filter.stopWatching(); }
|
||||
delete filters[call.name];
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(callback) !== 'function') {
|
||||
throw new Error('invalid callback');
|
||||
}
|
||||
|
||||
// Already have a filter, just update the callback
|
||||
if (info) {
|
||||
info.callback = callback;
|
||||
return;
|
||||
}
|
||||
|
||||
info = {callback: callback};
|
||||
filters[call.name] = info;
|
||||
|
||||
// Start a new filter
|
||||
/*
|
||||
info.filter = web3.eth.filter({
|
||||
address: contractAddress,
|
||||
topics: call.topics
|
||||
}, function(error, result) {
|
||||
// @TODO: Emit errors to .onerror? Maybe?
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
info.callback.apply(self, call.parse(result.data));
|
||||
} catch(error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
function runMethod(method, estimateOnly) {
|
||||
return function() {
|
||||
var provider = wallet._provider;
|
||||
|
||||
var transaction = {}
|
||||
|
||||
var params = Array.prototype.slice.call(arguments);
|
||||
if (params.length == contractInterface[method].inputs.length + 1) {
|
||||
transaction = params.pop();
|
||||
if (typeof(transaction) !== 'object') {
|
||||
throw new Error('invalid transaction overrides');
|
||||
}
|
||||
for (var key in transaction) {
|
||||
if (!allowedTransactionKeys[key]) {
|
||||
throw new Error('unknown transaction override ' + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var call = contractInterface[method].apply(contractInterface, params);
|
||||
switch (call.type) {
|
||||
case 'call':
|
||||
['data', 'gasLimit', 'gasPrice', 'to', 'value'].forEach(function(key) {
|
||||
if (transaction[key] != null) {
|
||||
throw new Error('call cannot override ' + key) ;
|
||||
}
|
||||
});
|
||||
transaction.data = call.data;
|
||||
if (transaction.from == null) {
|
||||
transaction.from = wallet.address;
|
||||
}
|
||||
transaction.to = contractAddress;
|
||||
|
||||
if (estimateOnly) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
resolve(new utils.BN(0));
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
provider.call(transaction).then(function(value) {
|
||||
resolve(call.parse(value));
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
case 'transaction':
|
||||
['data', 'from', 'to'].forEach(function(key) {
|
||||
if (transaction[key] != null) {
|
||||
throw new Error('transaction cannot override ' + key) ;
|
||||
}
|
||||
});
|
||||
transaction.data = call.data;
|
||||
transaction.to = contractAddress;
|
||||
if (transaction.gasLimit == null) {
|
||||
transaction.gasLimit = 3000000;
|
||||
}
|
||||
|
||||
if (estimateOnly) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
provider.estimateGas(transaction).then(function(gasEstimate) {
|
||||
resolve(gasEstimate);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
Promise.all([
|
||||
provider.getTransactionCount(wallet.address, 'pending'),
|
||||
provider.getGasPrice(),
|
||||
]).then(function(results) {
|
||||
if (transaction.nonce == null) {
|
||||
transaction.nonce = results[0];
|
||||
} else if (console.warn) {
|
||||
console.warn('Overriding suggested nonce: ' + results[0]);
|
||||
}
|
||||
if (transaction.gasPrice == null) {
|
||||
transaction.gasPrice = results[1];
|
||||
} else if (console.warn) {
|
||||
console.warn('Overriding suggested gasPrice: ' + utils.hexlify(results[1]));
|
||||
}
|
||||
|
||||
var signedTransaction = wallet.sign(transaction);
|
||||
provider.sendTransaction(signedTransaction).then(function(txid) {
|
||||
resolve(txid);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var estimate = {};
|
||||
utils.defineProperty(this, 'estimate', estimate);
|
||||
|
||||
contractInterface.methods.forEach(function(method) {
|
||||
utils.defineProperty(this, method, runMethod(method, false));
|
||||
utils.defineProperty(estimate, method, runMethod(method, true));
|
||||
}, this);
|
||||
|
||||
contractInterface.events.forEach(function(method) {
|
||||
var call = contractInterface[method].apply(contractInterface, []);
|
||||
Object.defineProperty(self, 'on' + call.name.toLowerCase(), {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
//console.log('get');
|
||||
var info = filters[call.name];
|
||||
if (!info || !info[call.name]) { return null; }
|
||||
return info.callback;
|
||||
},
|
||||
set: function(value) {
|
||||
//console.log('set');
|
||||
setupFilter(call, value);
|
||||
}
|
||||
});
|
||||
}, this);
|
||||
}
|
||||
utils.defineProperty(Contract, 'Interface', Interface);
|
||||
|
||||
module.exports = Contract;
|
||||
|
||||
331
lib/providers.js
Normal file
331
lib/providers.js
Normal file
@@ -0,0 +1,331 @@
|
||||
'use strict';
|
||||
|
||||
var inherits = require('inherits');
|
||||
var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
|
||||
|
||||
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) {
|
||||
var responseError = new Error('invalid response');
|
||||
responseError.orginialError = error;
|
||||
responseError.data = result;
|
||||
callback(responseError);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
return postProcess(this.client, 'eth_getBalance', [
|
||||
utils.getAddress(address),
|
||||
validBlock(blockNumber)
|
||||
], true);
|
||||
});
|
||||
|
||||
utils.defineProperty(SendAsyncProvider.prototype, 'getTransactionCount', function(address, blockNumber) {
|
||||
return postProcess(this.client, 'eth_getTransactionCount', [
|
||||
utils.getAddress(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');
|
||||
}
|
||||
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://api.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 = utils.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 = utils.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() {
|
||||
var query = ('module=proxy&action=eth_gasPrice');
|
||||
return this._send(query, hexToBN);
|
||||
});
|
||||
|
||||
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 = utils.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) {
|
||||
var address = utils.getAddress(transaction.to);
|
||||
|
||||
var query = 'module=proxy&action=eth_estimateGas&to=' + address;
|
||||
if (transaction.gasPrice) {
|
||||
query += '&gasPrice=' + utils.hexlify(transaction.gasPrice);
|
||||
}
|
||||
if (transaction.gasLimit) {
|
||||
query += '&gas=' + utils.hexlify(transaction.gasLimit);
|
||||
}
|
||||
if (transaction.from) {
|
||||
query += '&from=' + utils.getAddress(transaction.from);
|
||||
}
|
||||
if (transaction.data) {
|
||||
query += '&data=' + ensureHex(transaction.data);
|
||||
}
|
||||
if (transaction.value) {
|
||||
query += '&value=' + utils.hexlify(transaction.value);
|
||||
}
|
||||
return this._send(query, hexToBN);
|
||||
});
|
||||
|
||||
|
||||
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;
|
||||
4
lib/random-bytes.js
Normal file
4
lib/random-bytes.js
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = require('crypto').randomBytes;
|
||||
|
||||
74
lib/randomish.js
Normal file
74
lib/randomish.js
Normal file
@@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
var aes = require('aes-js');
|
||||
var randomBytes = require('./random-bytes.js');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
function Randomish() {
|
||||
if (!(this instanceof Randomish)) { throw new Error('missing new'); }
|
||||
|
||||
var weak = (randomBytes._weakCrypto || false);
|
||||
|
||||
var entropyBits = (weak ? 0: ((32 + 16) * 8));
|
||||
Object.defineProperty(this, 'entropy', {
|
||||
enumerable: true,
|
||||
get: function() { return entropyBits; }
|
||||
});
|
||||
|
||||
var entropy = new aes.ModeOfOperation.cbc(
|
||||
Randomish.randomishBytes(32),
|
||||
Randomish.randomishBytes(16)
|
||||
);
|
||||
|
||||
utils.defineProperty(this, 'feedEntropy', function(data, expectedEntropyBits) {
|
||||
if (!data) { data = ''; }
|
||||
if (!expectedEntropyBits) { expectedEntropyBits = 0; }
|
||||
|
||||
if (parseInt(expectedEntropyBits) != expectedEntropyBits) {
|
||||
throw new Error('invalid expectedEntropyBits');
|
||||
}
|
||||
|
||||
data = (new Date()).getTime() + '-' + JSON.stringify(data) + '-' + data.toString();
|
||||
var hashed = utils.sha3(new Buffer(data, 'utf8'));
|
||||
|
||||
entropyBits += expectedEntropyBits + (weak ? 0: ((32) * 8));
|
||||
|
||||
// Feed the hashed data and random data to the mode of operation
|
||||
entropy.encrypt(hashed.slice(0, 16));
|
||||
entropy.encrypt(randomBytes(16));
|
||||
entropy.encrypt(hashed.slice(0, 16));
|
||||
return new Buffer(entropy.encrypt(randomBytes(16)));
|
||||
});
|
||||
|
||||
utils.defineProperty(this, 'randomBytes', function(length, key) {
|
||||
if (parseInt(length) != length || length <= 0 || length > 1024) {
|
||||
throw new Error('invalid length');
|
||||
}
|
||||
|
||||
// If we don't have a key, create one
|
||||
if (!key) {
|
||||
key = Buffer.concat([this.feedEntropy(), this.feedEntropy()]);
|
||||
}
|
||||
|
||||
if (!Buffer.isBuffer(key) || key.length !== 32) {
|
||||
throw new Error('invalid key');
|
||||
}
|
||||
|
||||
var aesCbc = new aes.ModeOfOperation.cbc(key, this.feedEntropy());
|
||||
var result = new Buffer(0);
|
||||
while (result.length < length) {
|
||||
result = Buffer.concat([result, this.feedEntropy()]);
|
||||
}
|
||||
|
||||
return result.slice(0, length);
|
||||
});
|
||||
|
||||
this.feedEntropy();
|
||||
}
|
||||
|
||||
utils.defineProperty(Randomish, 'randomishBytes', function(length) {
|
||||
return randomBytes(length);
|
||||
});
|
||||
|
||||
module.exports = Randomish
|
||||
345
lib/secret-storage.js
Normal file
345
lib/secret-storage.js
Normal file
@@ -0,0 +1,345 @@
|
||||
'use strict';
|
||||
|
||||
var aes = require('aes-js');
|
||||
var pbkdf2 = require('pbkdf2');
|
||||
var scrypt = require('scrypt-js');
|
||||
var uuid = require('uuid');
|
||||
|
||||
var Randomish = require('./randomish.js');
|
||||
var SigningKey = require('./signing-key.js');
|
||||
var utils = require('./utils.js')
|
||||
|
||||
|
||||
// Search an Object and its children recursively, caselessly.
|
||||
function searchPath(object, path) {
|
||||
var currentChild = object;
|
||||
|
||||
var comps = path.toLowerCase().split('/');
|
||||
for (var i = 0; i < comps.length; i++) {
|
||||
|
||||
// Search for a child object with a case-insensitive matching key
|
||||
var matchingChild = null;
|
||||
for (var key in currentChild) {
|
||||
if (key.toLowerCase() === comps[i]) {
|
||||
matchingChild = currentChild[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't find one. :'(
|
||||
if (matchingChild === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Now check this child...
|
||||
currentChild = matchingChild;
|
||||
}
|
||||
|
||||
return currentChild;
|
||||
}
|
||||
|
||||
/*
|
||||
function SecretStorage(json, signingKey) {
|
||||
if (!(this instanceof SecretStorage)) { throw new Error('missing new'); }
|
||||
|
||||
utils.defineProperty(this, 'json', json);
|
||||
|
||||
Object.defineProperty(this, 'data', {
|
||||
enumerable: true,
|
||||
get: function() { return JSON.parse(json); }
|
||||
});
|
||||
|
||||
utils.defineProperty(this, 'address', signingKey.privateKey);
|
||||
utils.defineProperty(this, 'signingKey', signingKey);
|
||||
}
|
||||
*/
|
||||
var secretStorage = {};
|
||||
|
||||
|
||||
utils.defineProperty(secretStorage, 'isCrowdsaleWallet', function(json) {
|
||||
try {
|
||||
var data = JSON.parse(json);
|
||||
} catch (error) { return false; }
|
||||
|
||||
return (data.encseed && data.ethaddr);
|
||||
});
|
||||
|
||||
utils.defineProperty(secretStorage, 'isValidWallet', function(json) {
|
||||
try {
|
||||
var data = JSON.parse(json);
|
||||
} catch (error) { return false; }
|
||||
|
||||
if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) {
|
||||
return false;
|
||||
}
|
||||
// @TODO: Put more checks to make sure it has kdf, iv and all that good stuff
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// See: https://github.com/ethereum/pyethsaletool
|
||||
utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password) {
|
||||
var data = JSON.parse(json);
|
||||
|
||||
// Ethereum Address
|
||||
var ethaddr = utils.getAddress(searchPath(data, 'ethaddr'));
|
||||
|
||||
// Encrypted Seed
|
||||
var encseed = new Buffer(searchPath(data, 'encseed'), 'hex');
|
||||
if (!encseed || (encseed.length % 16) !== 0) {
|
||||
throw new Error('invalid encseed');
|
||||
}
|
||||
|
||||
var key = pbkdf2.pbkdf2Sync(password, password, 2000, 32, 'sha256').slice(0, 16);
|
||||
|
||||
var iv = encseed.slice(0, 16);
|
||||
var encryptedSeed = encseed.slice(16);
|
||||
|
||||
// Decrypt the seed
|
||||
var seed = new Buffer(0);
|
||||
var aesCbc = new aes.ModeOfOperation.cbc(key, iv);
|
||||
for (var i = 0; i < encryptedSeed.length; i += 16) {
|
||||
seed = Buffer.concat([seed, new Buffer(aesCbc.decrypt(encryptedSeed.slice(i, i + 16)))]);
|
||||
}
|
||||
|
||||
// Check PKCS#7 padding is valid
|
||||
var pad = seed[seed.length - 1];
|
||||
if (pad > 16 || pad > seed.length) {
|
||||
throw new Error('invalid password');
|
||||
}
|
||||
for (var i = seed.length - pad; i < seed.length; i++) {
|
||||
if (seed[i] !== pad) {
|
||||
throw new Error('invalid password');
|
||||
}
|
||||
}
|
||||
|
||||
// Strip the padding
|
||||
seed = seed.slice(0, seed.length - pad);
|
||||
|
||||
// This wallet format is weird... Convert the binary encoded hex to a string.
|
||||
var seedHex = '';
|
||||
for (var i = 0; i < seed.length; i++) {
|
||||
seedHex += String.fromCharCode(seed[i]);
|
||||
}
|
||||
|
||||
var signingKey = new SigningKey(utils.sha3(new Buffer(seedHex)));
|
||||
|
||||
if (signingKey.address !== ethaddr) {
|
||||
throw new Error('corrupt crowdsale wallet');
|
||||
}
|
||||
|
||||
return signingKey;
|
||||
});
|
||||
|
||||
|
||||
utils.defineProperty(secretStorage, 'decrypt', function(json, password, progressCallback) {
|
||||
if (!Buffer.isBuffer(password)) { throw new Error('password must be a buffer'); }
|
||||
|
||||
var data = JSON.parse(json);
|
||||
|
||||
var decrypt = function(key, ciphertext) {
|
||||
var cipher = searchPath(data, 'crypto/cipher');
|
||||
if (cipher === 'aes-128-ctr') {
|
||||
var iv = new Buffer(searchPath(data, 'crypto/cipherparams/iv'), 'hex')
|
||||
var counter = new aes.Counter(iv);
|
||||
|
||||
var aesCtr = new aes.ModeOfOperation.ctr(key, counter);
|
||||
|
||||
return new Buffer(aesCtr.decrypt(ciphertext));
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
var computeMAC = function(derivedHalf, ciphertext) {
|
||||
return utils.sha3(Buffer.concat([derivedHalf, ciphertext]));
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var kdf = searchPath(data, 'crypto/kdf');
|
||||
if (kdf && kdf.toLowerCase() === 'scrypt') {
|
||||
var salt = new Buffer(searchPath(data, 'crypto/kdfparams/salt'), 'hex');
|
||||
var N = parseInt(searchPath(data, 'crypto/kdfparams/n'));
|
||||
var r = parseInt(searchPath(data, 'crypto/kdfparams/r'));
|
||||
var p = parseInt(searchPath(data, 'crypto/kdfparams/p'));
|
||||
if (!N || !r || !p) {
|
||||
reject(new Error('unsupported key-derivation function parameters'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure N is a power of 2
|
||||
if ((N & (N - 1)) !== 0) {
|
||||
reject(new Error('unsupported key-derivation function parameter value for N'));
|
||||
return;
|
||||
}
|
||||
|
||||
var dkLen = searchPath(data, 'crypto/kdfparams/dklen');
|
||||
if (dkLen !== 32) {
|
||||
reject( new Error('unsupported key-derivation derived-key length'));
|
||||
return;
|
||||
}
|
||||
|
||||
scrypt(password, salt, N, r, p, dkLen, function(error, progress, key) {
|
||||
if (error) {
|
||||
error.progress = progress;
|
||||
reject(error);
|
||||
|
||||
} else if (key) {
|
||||
key = new Buffer(key);
|
||||
|
||||
var ciphertext = new Buffer(searchPath(data, 'crypto/ciphertext'), 'hex');
|
||||
|
||||
var computedMAC = computeMAC(key.slice(16, 32), ciphertext).toString('hex').toLowerCase();
|
||||
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
|
||||
reject(new Error('invalid password'));
|
||||
return;
|
||||
}
|
||||
|
||||
var privateKey = decrypt(key.slice(0, 16), ciphertext);
|
||||
|
||||
if (!privateKey) {
|
||||
reject(new Error('unsupported cipher'));
|
||||
return;
|
||||
}
|
||||
|
||||
var signingKey = new SigningKey(privateKey);
|
||||
if (signingKey.address !== utils.getAddress(data.address)) {
|
||||
reject(new Error('address mismatch'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (progressCallback) { progressCallback(1); }
|
||||
resolve(signingKey);
|
||||
|
||||
} else if (progressCallback) {
|
||||
return progressCallback(progress);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
// @TOOD: Support pbkdf2 kdf
|
||||
reject(new Error('unsupported key-derivation function'));
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(secretStorage, 'encrypt', function(privateKey, password, options, progressCallback) {
|
||||
|
||||
// the options are optional, so adjust the call as needed
|
||||
if (typeof(options) === 'function' && !progressCallback) {
|
||||
progressCallback = options;
|
||||
options = {};
|
||||
}
|
||||
if (!options) { options = {}; }
|
||||
|
||||
// Check the private key
|
||||
if (privateKey instanceof SigningKey) {
|
||||
privateKey = privateKey.privateKey;
|
||||
}
|
||||
privateKey = utils.hexOrBuffer(privateKey, 'private key');
|
||||
if (privateKey.length !== 32) { throw new Erro('invalid private key'); }
|
||||
|
||||
// Check the password
|
||||
if (!Buffer.isBuffer(password)) { throw new Error('password must be a buffer'); }
|
||||
|
||||
// Check/generate the salt
|
||||
var salt = options.salt;
|
||||
if (salt) {
|
||||
salt = utils.hexOrBuffer(salt, 'salt');
|
||||
} else {
|
||||
salt = (new Randomish()).randomBytes(32);;
|
||||
}
|
||||
|
||||
// Override initialization vector
|
||||
var iv = null;
|
||||
if (options.iv) {
|
||||
iv = utils.hexOrBuffer(options.iv, 'iv');
|
||||
if (iv.length !== 16) { throw new Error('invalid iv'); }
|
||||
}
|
||||
|
||||
// Override the uuid
|
||||
var uuidRandom = options.uuid;
|
||||
if (uuidRandom) {
|
||||
uuidRandom = utils.hexOrBuffer(uuidRandom, 'uuid');
|
||||
if (uuidRandom.length !== 16) { throw new Error('invalid uuid'); }
|
||||
}
|
||||
|
||||
// Override the scrypt password-based key derivation function parameters
|
||||
var N = (1 << 17), r = 8, p = 1;
|
||||
if (options.scrypt) {
|
||||
if (options.scrypt.N) { N = options.scrypt.N; }
|
||||
if (options.scrypt.r) { r = options.scrypt.r; }
|
||||
if (options.scrypt.p) { p = options.scrypt.p; }
|
||||
}
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
// We take 64 bytes:
|
||||
// - 32 bytes As normal for the Web3 secret storage (derivedKey, macPrefix)
|
||||
// - 16 bytes The initialization vector
|
||||
// - 16 bytes The UUID random bytes
|
||||
scrypt(password, salt, N, r, p, 64, function(error, progress, key) {
|
||||
if (error) {
|
||||
error.progress = progress;
|
||||
reject(error);
|
||||
|
||||
} else if (key) {
|
||||
// Convert the array-like to a Buffer
|
||||
key = new Buffer(key);
|
||||
|
||||
// These will be used to encrypt the wallet (as per Web3 secret storage)
|
||||
var derivedKey = key.slice(0, 16);
|
||||
var macPrefix = key.slice(16, 32);
|
||||
|
||||
// Get the initialization vector
|
||||
if (!iv) { iv = key.slice(32, 48); }
|
||||
|
||||
// Get the UUID random data
|
||||
if (!uuidRandom) { uuidRandom = key.slice(48, 64); }
|
||||
|
||||
// Get the address for this private key
|
||||
var address = (new SigningKey(privateKey)).address;
|
||||
|
||||
// Encrypt the private key
|
||||
var counter = new aes.Counter(iv);
|
||||
var aesCtr = new aes.ModeOfOperation.ctr(derivedKey, counter);
|
||||
var ciphertext = new Buffer(aesCtr.encrypt(privateKey));
|
||||
|
||||
// Compute the message authentication code, used to check the password
|
||||
var mac = utils.sha3(Buffer.concat([macPrefix, ciphertext]))
|
||||
|
||||
// See: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition
|
||||
var data = {
|
||||
address: address.substring(2).toLowerCase(),
|
||||
id: uuid.v4({random: uuidRandom}),
|
||||
version: 3,
|
||||
Crypto: {
|
||||
cipher: 'aes-128-ctr',
|
||||
cipherparams: {
|
||||
iv: iv.toString('hex')
|
||||
},
|
||||
ciphertext: ciphertext.toString('hex'),
|
||||
kdf: 'scrypt',
|
||||
kdfparams: {
|
||||
salt: salt.toString('hex'),
|
||||
n: N,
|
||||
dklen: 32,
|
||||
p: p,
|
||||
r: r
|
||||
},
|
||||
mac: mac.toString('hex')
|
||||
}
|
||||
};
|
||||
|
||||
if (progressCallback) { progressCallback(1); }
|
||||
resolve(JSON.stringify(data));
|
||||
|
||||
} else if (progressCallback) {
|
||||
return progressCallback(progress);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = secretStorage;
|
||||
38
lib/signing-key.js
Normal file
38
lib/signing-key.js
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
var elliptic = require('elliptic');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var secp256k1 = new (elliptic.ec)('secp256k1');
|
||||
|
||||
|
||||
|
||||
function SigningKey(privateKey) {
|
||||
if (!(this instanceof SigningKey)) { throw new Error('missing new'); }
|
||||
|
||||
if (utils.isHexString(privateKey, 32)) {
|
||||
privateKey = utils.hexOrBuffer(privateKey);
|
||||
} else if (!Buffer.isBuffer(privateKey) || privateKey.length !== 32) {
|
||||
throw new Error('invalid private key');
|
||||
}
|
||||
utils.defineProperty(this, 'privateKey', '0x' + privateKey.toString('hex'))
|
||||
|
||||
var keyPair = secp256k1.keyFromPrivate(privateKey);
|
||||
var publicKey = (new Buffer(keyPair.getPublic(false, 'hex'), 'hex')).slice(1);
|
||||
|
||||
var address = utils.getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
utils.defineProperty(this, 'address', address)
|
||||
|
||||
utils.defineProperty(this, 'signDigest', function(digest) {
|
||||
return keyPair.sign(digest, {canonical: true});
|
||||
});
|
||||
}
|
||||
|
||||
utils.defineProperty(SigningKey, 'recover', function(digest, r, s, recoveryParam) {
|
||||
var publicKey = secp256k1.recoverPubKey(digest, {r: r, s: s}, recoveryParam);
|
||||
publicKey = (new Buffer(publicKey.encode('hex', false), 'hex')).slice(1);
|
||||
return utils.getAddress(utils.sha3(publicKey).slice(12).toString('hex'));
|
||||
});
|
||||
|
||||
module.exports = SigningKey;
|
||||
78
lib/units.js
Normal file
78
lib/units.js
Normal file
@@ -0,0 +1,78 @@
|
||||
var utils = require('./utils.js');
|
||||
|
||||
var zero = new utils.BN(0);
|
||||
var negative1 = new utils.BN(-1);
|
||||
var tenPower18 = new utils.BN('1000000000000000000');
|
||||
|
||||
function formatEther(wei, options) {
|
||||
|
||||
if (typeof(wei) === 'number') {
|
||||
// @TODO: Warn if truncation will occur?
|
||||
wei = new utils.BN(wei);
|
||||
} else if (utils.isHexString(wei)) {
|
||||
wei = new utils.BN(wei.substring(2), 16);
|
||||
}
|
||||
|
||||
if (!options) { options = {}; }
|
||||
|
||||
if (!(wei instanceof utils.BN)) { throw new Error('invalid wei'); }
|
||||
|
||||
var negative = wei.lt(zero);
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
var fraction = wei.mod(tenPower18).toString(10);
|
||||
while (fraction.length < 18) { fraction = '0' + fraction; }
|
||||
|
||||
if (!options.pad) {
|
||||
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
|
||||
}
|
||||
|
||||
var whole = wei.div(tenPower18).toString(10);
|
||||
|
||||
if (options.commify) {
|
||||
whole = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ",")
|
||||
}
|
||||
|
||||
var value = whole + '.' + fraction;
|
||||
|
||||
if (negative) { value = '-' + value; }
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseEther(ether) {
|
||||
if (typeof(ether) !== 'string' || !ether.match(/^-?[0-9.]+$/)) {
|
||||
throw new Error('invalid value');
|
||||
}
|
||||
|
||||
// Is it negative?
|
||||
var negative = (ether.substring(0, 1) === '-');
|
||||
if (negative) { ether = ether.substring(1); }
|
||||
|
||||
if (ether === '.') { throw new Error('invalid value'); }
|
||||
|
||||
// Split it into a whole and fractional part
|
||||
var comps = ether.split('.');
|
||||
if (comps.length > 2) { throw new Error('too many decimal points'); }
|
||||
|
||||
var whole = comps[0], fraction = comps[1];
|
||||
if (!whole) { whole = '0'; }
|
||||
if (!fraction) { fraction = '0'; }
|
||||
if (fraction.length > 18) { throw new Error('too many decimal places'); }
|
||||
|
||||
while (fraction.length < 18) { fraction += '0'; }
|
||||
|
||||
whole = new utils.BN(whole);
|
||||
fraction = new utils.BN(fraction);
|
||||
|
||||
var wei = (whole.mul(tenPower18)).add(fraction);
|
||||
|
||||
if (negative) { wei = wei.mul(negative1); }
|
||||
|
||||
return wei;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
formatEther: formatEther,
|
||||
parseEther: parseEther,
|
||||
}
|
||||
150
lib/utils.js
150
lib/utils.js
@@ -1,5 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
var rlp = require('rlp');
|
||||
|
||||
var BN = require('../node_modules/elliptic/node_modules/bn.js/lib/bn.js');
|
||||
var hash = require('../node_modules/elliptic/node_modules/hash.js/lib/hash.js');
|
||||
|
||||
|
||||
// See: https://github.com/emn178/js-sha3
|
||||
@@ -180,8 +184,18 @@ function keccak(message) {
|
||||
};
|
||||
|
||||
// Quick sanity check
|
||||
if (keccak(new Buffer('ricmoo')) !== 'b05e424817fb90aa7a79e9da5c5f94070a316219c6ebb863a9ff7ca357dc9fa9') {
|
||||
throw new Error('problem with sh3?!');
|
||||
//if (keccak(new Buffer('ricmoo')) !== 'b05e424817fb90aa7a79e9da5c5f94070a316219c6ebb863a9ff7ca357dc9fa9') {
|
||||
// throw new Error('problem with sh3?!');
|
||||
//}
|
||||
|
||||
function sha256(data) {
|
||||
if (typeof(data) === 'string') {
|
||||
data = new Buffer(data, 'utf8');
|
||||
} else if (!Buffer.isBuffer(data)) {
|
||||
throw new Error('must be a sting');
|
||||
}
|
||||
|
||||
return (new Buffer(hash.sha256().update(data).digest('hex'), 'hex'));
|
||||
}
|
||||
|
||||
function sha3(data) {
|
||||
@@ -201,6 +215,119 @@ function defineProperty(object, name, value) {
|
||||
});
|
||||
}
|
||||
|
||||
function getChecksumAddress(address) {
|
||||
if (typeof(address) !== 'string' || !address.match(/^0x[0-9A-Fa-f]{40}$/)) {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
address = address.substring(2).toLowerCase();
|
||||
var hashed = sha3(address);
|
||||
|
||||
address = address.split('');
|
||||
for (var i = 0; i < 40; i += 2) {
|
||||
if ((hashed[i >> 1] >> 4) >= 8) {
|
||||
address[i] = address[i].toUpperCase();
|
||||
}
|
||||
if ((hashed[i >> 1] & 0x0f) >= 8) {
|
||||
address[i + 1] = address[i + 1].toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
return '0x' + address.join('');
|
||||
}
|
||||
|
||||
function getAddress(address) {
|
||||
var result = null;
|
||||
|
||||
if (typeof(address) !== 'string') { throw new Error('invalid address'); }
|
||||
|
||||
if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
|
||||
|
||||
// Missing the 0x prefix
|
||||
if (address.substring(0, 2) !== '0x') { address = '0x' + address; }
|
||||
|
||||
result = getChecksumAddress(address);
|
||||
|
||||
// It is a checksummed address with a bad checksum
|
||||
if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
|
||||
throw new Error('invalid address checksum');
|
||||
}
|
||||
|
||||
// Maybe ICAP? (we only support direct mode)
|
||||
} else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
|
||||
|
||||
// It is an ICAP address with a bad checksum
|
||||
if (address.substring(2, 4) !== ibanChecksum(address)) {
|
||||
throw new Error('invalid address icap checksum');
|
||||
}
|
||||
|
||||
result = (new BN(address.substring(4), 36)).toString(16);
|
||||
while (result.length < 40) { result = '0' + result; }
|
||||
result = getChecksumAddress('0x' + result);
|
||||
|
||||
} else {
|
||||
throw new Error('invalid address');
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number
|
||||
var ibanChecksum = (function() {
|
||||
|
||||
// Create lookup table
|
||||
var ibanLookup = {};
|
||||
for (var i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
|
||||
for (var i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
|
||||
|
||||
// How many decimal digits can we process? (for 64-bit float, this is 15)
|
||||
var safeDigits = Math.floor(Math.log10(Number.MAX_SAFE_INTEGER));
|
||||
|
||||
return function(address) {
|
||||
address = address.toUpperCase();
|
||||
address = address.substring(4) + address.substring(0, 2) + '00';
|
||||
|
||||
var expanded = address.split('');
|
||||
for (var i = 0; i < expanded.length; i++) {
|
||||
expanded[i] = ibanLookup[expanded[i]];
|
||||
}
|
||||
expanded = expanded.join('');
|
||||
|
||||
// Javascript can handle integers safely up to 15 (decimal) digits
|
||||
while (expanded.length >= safeDigits){
|
||||
var block = expanded.substring(0, safeDigits);
|
||||
expanded = parseInt(block, 10) % 97 + expanded.substring(block.length);
|
||||
}
|
||||
|
||||
var checksum = String(98 - (parseInt(expanded, 10) % 97));
|
||||
while (checksum.length < 2) { checksum = '0' + checksum; }
|
||||
|
||||
return checksum;
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
function getIcapAddress(address) {
|
||||
address = getAddress(address).substring(2);
|
||||
var base36 = (new BN(address, 16)).toString(36).toUpperCase();
|
||||
while (base36.length < 30) { base36 = '0' + base36; }
|
||||
return 'XE' + ibanChecksum('XE00' + base36) + base36;
|
||||
}
|
||||
|
||||
// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
|
||||
function getContractAddress(transaction) {
|
||||
return getAddress('0x' + sha3(rlp.encode([
|
||||
hexOrBuffer(getAddress(transaction.from)),
|
||||
hexOrBuffer(hexlify(transaction.nonce, 'nonce'))
|
||||
])).slice(12).toString('hex'));
|
||||
}
|
||||
|
||||
function cloneObject(object) {
|
||||
var clone = {};
|
||||
for (var key in object) { clone[key] = object[key]; }
|
||||
return clone;
|
||||
}
|
||||
|
||||
function stripZeros(buffer) {
|
||||
var i = 0;
|
||||
for (i = 0; i < buffer.length; i++) {
|
||||
@@ -215,9 +342,17 @@ function bnToBuffer(bn) {
|
||||
return stripZeros(new Buffer(hex, 'hex'));
|
||||
}
|
||||
|
||||
function isHexString(value, length) {
|
||||
if (typeof(value) !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) {
|
||||
return false
|
||||
}
|
||||
if (length && value.length !== 2 + 2 * length) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
function hexOrBuffer(value, name) {
|
||||
if (!Buffer.isBuffer(value)) {
|
||||
if (typeof(value) !== 'string' || !value.match(/^0x[0-9A-Fa-f]*$/)) {
|
||||
if (!isHexString(value)) {
|
||||
var error = new Error(name ? ('invalid ' + name) : 'invalid hex or buffer');
|
||||
error.reason = 'invalid hex string';
|
||||
error.value = value;
|
||||
@@ -249,10 +384,19 @@ module.exports = {
|
||||
|
||||
defineProperty: defineProperty,
|
||||
|
||||
getAddress: getAddress,
|
||||
getIcapAddress: getIcapAddress,
|
||||
|
||||
getContractAddress: getContractAddress,
|
||||
|
||||
cloneObject: cloneObject,
|
||||
|
||||
bnToBuffer: bnToBuffer,
|
||||
isHexString: isHexString,
|
||||
hexOrBuffer: hexOrBuffer,
|
||||
hexlify: hexlify,
|
||||
stripZeros: stripZeros,
|
||||
|
||||
sha256: sha256,
|
||||
sha3: sha3,
|
||||
}
|
||||
|
||||
283
lib/wallet.js
Normal file
283
lib/wallet.js
Normal file
@@ -0,0 +1,283 @@
|
||||
'use strict';
|
||||
|
||||
var rlp = require('rlp');
|
||||
|
||||
var Contract = require('./contract.js');
|
||||
var providers = require('./providers.js');
|
||||
var SigningKey = require('./signing-key.js');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
// This ensures we inject a setImmediate into the global space, which
|
||||
// dramatically improves the performance of the scrypt PBKDF.
|
||||
require('setimmediate');
|
||||
|
||||
var transactionFields = [
|
||||
{name: 'nonce', maxLength: 32, },
|
||||
{name: 'gasPrice', maxLength: 32, },
|
||||
{name: 'gasLimit', maxLength: 32, },
|
||||
{name: 'to', length: 20, },
|
||||
{name: 'value', maxLength: 32, },
|
||||
{name: 'data'},
|
||||
];
|
||||
|
||||
function Wallet(privateKey, provider) {
|
||||
if (!(this instanceof Wallet)) { throw new Error('missing new'); }
|
||||
|
||||
// Make sure we have a valid signing key
|
||||
var signingKey = privateKey;
|
||||
if (!(privateKey instanceof SigningKey)) {
|
||||
signingKey = new SigningKey(privateKey);
|
||||
}
|
||||
utils.defineProperty(this, 'privateKey', signingKey.privateKey);
|
||||
|
||||
Object.defineProperty(this, 'provider', {
|
||||
enumerable: true,
|
||||
get: function() { return provider; },
|
||||
set: function(value) {
|
||||
if (value !== null && !providers.isProvider(value)) {
|
||||
throw new Error('invalid provider');
|
||||
}
|
||||
provider = value;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, '_provider', {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
if (!provider) { throw new Error('missing provider'); }
|
||||
return provider;
|
||||
},
|
||||
});
|
||||
|
||||
if (provider !== null) {
|
||||
|
||||
// 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, 'sign', function(transaction) {
|
||||
var raw = [];
|
||||
transactionFields.forEach(function(fieldInfo) {
|
||||
var value = transaction[fieldInfo.name] || (new Buffer(0));
|
||||
value = utils.hexOrBuffer(utils.hexlify(value), fieldInfo.name);
|
||||
|
||||
// Fixed-width field
|
||||
if (fieldInfo.length && value.length !== fieldInfo.length && value.length > 0) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'wrong length';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Variable-width (with a maximum)
|
||||
if (fieldInfo.maxLength) {
|
||||
value = utils.stripZeros(value);
|
||||
if (value.length > fieldInfo.maxLength) {
|
||||
var error = new Error('invalid ' + fieldInfo.name);
|
||||
error.reason = 'too long';
|
||||
error.value = value;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
raw.push(value);
|
||||
});
|
||||
|
||||
var digest = utils.sha3(rlp.encode(raw));
|
||||
|
||||
var signature = signingKey.signDigest(digest);
|
||||
var s = signature.s;
|
||||
var v = signature.recoveryParam;
|
||||
|
||||
raw.push(new Buffer([27 + v]));
|
||||
raw.push(utils.bnToBuffer(signature.r));
|
||||
raw.push(utils.bnToBuffer(s));
|
||||
|
||||
return ('0x' + rlp.encode(raw).toString('hex'));
|
||||
});
|
||||
}
|
||||
|
||||
utils.defineProperty(Wallet, 'parseTransaction', function(rawTransaction) {
|
||||
rawTransaction = utils.hexOrBuffer(rawTransaction, 'rawTransaction');
|
||||
var signedTransaction = rlp.decode(rawTransaction);
|
||||
|
||||
var raw = [];
|
||||
|
||||
var transaction = {};
|
||||
transactionFields.forEach(function(fieldInfo, index) {
|
||||
transaction[fieldInfo.name] = signedTransaction[index];
|
||||
raw.push(signedTransaction[index]);
|
||||
});
|
||||
|
||||
if (transaction.to) {
|
||||
if (transaction.to.length === 0) {
|
||||
delete transaction.to;
|
||||
} else {
|
||||
transaction.to = utils.getAddress('0x' + transaction.to.toString('hex'));
|
||||
}
|
||||
}
|
||||
|
||||
['gasPrice', 'gasLimit', 'nonce', 'value'].forEach(function(name) {
|
||||
if (!transaction[name]) { return; }
|
||||
if (transaction[name].length === 0) {
|
||||
transaction[name] = new utils.BN(0);
|
||||
} else {
|
||||
transaction[name] = new utils.BN(transaction[name].toString('hex'), 16);
|
||||
}
|
||||
});
|
||||
|
||||
/* @TODO: Maybe? In the future, all nonces stored as numbers? (obviously, major version change)
|
||||
if (transaction.nonce) {
|
||||
transaction.nonce = transaction.nonce.toNumber()
|
||||
}
|
||||
*/
|
||||
|
||||
if (signedTransaction.length > 6 && signedTransaction[6].length === 1 &&
|
||||
signedTransaction[7].length >= 1 && signedTransaction[7].length <= 32 &&
|
||||
signedTransaction[8].length >= 1 && signedTransaction[7].length <= 32) {
|
||||
|
||||
transaction.v = signedTransaction[6][0];
|
||||
transaction.r = signedTransaction[7];
|
||||
transaction.s = signedTransaction[8];
|
||||
|
||||
var digest = utils.sha3(rlp.encode(raw));
|
||||
try {
|
||||
transaction.from = SigningKey.recover(digest, transaction.r, transaction.s, transaction.v - 27);
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
return transaction;
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'getBalance', function(blockNumber) {
|
||||
var provider = this._provider;
|
||||
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
provider.getBalance(self.address, blockNumber).then(function(balance) {
|
||||
resolve(balance);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'getTransactionCount', function(blockNumber) {
|
||||
var provider = this._provider;
|
||||
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
provider.getTransactionCount(self.address, blockNumber).then(function(transactionCount) {
|
||||
resolve(transactionCount);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'estimateGas', function(transaction) {
|
||||
var provider = this._provider;
|
||||
|
||||
transaction = utils.cloneObject(transaction);
|
||||
if (transaction.from == null) { transaction.from = this.address; }
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
provider.estimateGas(transaction).then(function(gasEstimate) {
|
||||
resolve(gasEstimate);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'sendTransaction', function(transaction) {
|
||||
var gasLimit = transaction.gasLimit;
|
||||
if (gasLimit == null) { gasLimit = 3000000; }
|
||||
|
||||
var self = this;
|
||||
|
||||
var provider = this._provider;
|
||||
|
||||
var gasPrice = new Promise(function(resolve, reject) {
|
||||
if (transaction.gasPrice) {
|
||||
return resolve(transaction.gasPrice);
|
||||
}
|
||||
provider.getGasPrice().then(function(gasPrice) {
|
||||
resolve(gasPrice);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
var nonce = new Promise(function(resolve, reject) {
|
||||
if (transaction.nonce) {
|
||||
return resolve(transaction.nonce);
|
||||
}
|
||||
provider.getTransactionCount(self.address, 'pending').then(function(transactionCount) {
|
||||
resolve(transactionCount);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
Promise.all([gasPrice, nonce]).then(function(results) {
|
||||
var signedTransaction = self.sign({
|
||||
to: transaction.to,
|
||||
gasLimit: gasLimit,
|
||||
gasPrice: results[0],
|
||||
nonce: results[1],
|
||||
value: transaction.value
|
||||
});
|
||||
|
||||
provider.sendTransaction(signedTransaction).then(function(txid) {
|
||||
resolve(txid);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'send', function(address, amountWei, options) {
|
||||
address = utils.getAddress(address);
|
||||
if (utils.BN.isBN(amountWei)) {
|
||||
amountWei = '0x' + utils.bnToBuffer(amountWei).toString('hex');
|
||||
}
|
||||
if (!utils.isHexString(amountWei)) { throw new Error('invalid amountWei'); }
|
||||
|
||||
if (!options) { options = {}; }
|
||||
|
||||
return this.sendTransaction({
|
||||
to: address,
|
||||
gasLimit: options.gasLimit,
|
||||
gasPrice: options.gasPrice,
|
||||
nonce: options.nonce,
|
||||
value: amountWei,
|
||||
});
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet.prototype, 'getContract', function(address, abi) {
|
||||
return new Contract(this, address, new Contract.Interface(abi));
|
||||
});
|
||||
|
||||
utils.defineProperty(Wallet, '_Contract', Contract);
|
||||
|
||||
module.exports = Wallet;
|
||||
28
package.json
28
package.json
@@ -1,32 +1,48 @@
|
||||
{
|
||||
"name": "ethers-wallet",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.5",
|
||||
"description": "Ethereum wallet library.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"test": "./node_modules/.bin/nodeunit tests/index.js",
|
||||
"version": "grunt dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"aes-js": "0.2.4",
|
||||
"aes-js": "2.0.0",
|
||||
"elliptic": "6.3.1",
|
||||
"inherits": "2.0.1",
|
||||
"pbkdf2": "3.0.4",
|
||||
"rlp": "2.0.0",
|
||||
"scrypt-js": "2.0.0",
|
||||
"uuid": "2.0.1"
|
||||
"setimmediate": "1.0.4",
|
||||
"scrypt-js": "2.0.3",
|
||||
"uuid": "2.0.1",
|
||||
"xmlhttprequest": "1.8.0"
|
||||
},
|
||||
"browser": {
|
||||
"./lib/random-bytes.js": "./lib/browser-random-bytes.js",
|
||||
"xmlhttprequest": "./lib/browser-xmlhttprequest.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ethereumjs-abi": "0.6.2",
|
||||
"ethereumjs-tx": "1.1.1",
|
||||
"ethereumjs-util": "4.3.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
"grunt-contrib-uglify": "^1.0.1",
|
||||
"web3": "0.15.3"
|
||||
"nodeunit": "0.9.1",
|
||||
"web3": "0.15.3",
|
||||
|
||||
"ethereumjs-vm": "1.4.0",
|
||||
"solc": "0.3.5"
|
||||
},
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
"wallet"
|
||||
],
|
||||
"author": "Richard Moore <me@ricmoo.com>",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/ethers-io/ethers-wallet.git"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
1
test-wallets/wallet-test-encseed-foo.json
Normal file
1
test-wallets/wallet-test-encseed-foo.json
Normal file
@@ -0,0 +1 @@
|
||||
{"encseed": "e695362f72ab0a364385f35a3435300188300ee5220c03dcbcf461c62432b6679ab163abf476aefa5f8cf366eea73c8b7e28e8c0a79ef819634bb8ee1875253d05b3825076c514063a7ccd8f0f79ba352799571f8ec8b749f8508a638a9a891d", "ethaddr": "2e326fa404fc3661de4f4361776ed9bbabdc26e3", "email": "me@ricmoo.com", "btcaddr": "1GW9wXcbtFNV6N6Mxje82Tmjk2iB9fffaj"}
|
||||
1
test-wallets/wallet-test-encseed-no-password.json
Normal file
1
test-wallets/wallet-test-encseed-no-password.json
Normal file
@@ -0,0 +1 @@
|
||||
{"encseed": "3a7cc89f7d53d44d6e6445a2cf7fa359598f7914e1b4f808a1b55a13ea4cc71a5529782ba169bb0b169b29e4160c2d4d3167ed282267634ae326379cdf400bf82bc9512be11169bc1822fef4a17b9cf4ad685544ddfc793eafee573650f19185", "ethaddr": "0b88d4b324ec24c8c078551e6e5075547157e5b6", "email": "me@ricmoo.com", "btcaddr": "13pey9DLvYxeftGaGF1xgTfPYNmhu1fbm3"}
|
||||
1
test-wallets/wallet-test-geth-foo.json
Normal file
1
test-wallets/wallet-test-geth-foo.json
Normal file
@@ -0,0 +1 @@
|
||||
{"address":"88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290","Crypto":{"cipher":"aes-128-ctr","ciphertext":"10adcc8bcaf49474c6710460e0dc974331f71ee4c7baa7314b4a23d25fd6c406","cipherparams":{"iv":"1dcdf13e49cea706994ed38804f6d171"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"bbfa53547e3e3bfcc9786a2cbef8504a5031d82734ecef02153e29daeed658fd"},"mac":"1cf53b5ae8d75f8c037b453e7c3c61b010225d916768a6b145adf5cf9cb3a703"},"id":"fb1280c0-d646-4e40-9550-7026b1be504a","version":3}
|
||||
1
test-wallets/wallet-test-geth-no-password.json
Normal file
1
test-wallets/wallet-test-geth-no-password.json
Normal file
@@ -0,0 +1 @@
|
||||
{"address":"4a9cf99357f5789251a8d7fad5b86d0f31eeb938","Crypto":{"cipher":"aes-128-ctr","ciphertext":"f93a875c23f5430bb86962da1ff2176d5b9179cfdd31e93027d67b0123e7a059","cipherparams":{"iv":"5fb35020cc5e84447a4b8a3a39543dbe"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"0d6bf5361b8c70745b8d03bb096876b27f00de2627b503b3f7c5ce9e8e70abe6"},"mac":"8f80f79e55cc36c231a00a69c43d603bd0cf8eb70bb9d77532cd7ddb56e4707e"},"id":"372b47d8-aba7-41a5-b3cd-a848b4f5aab1","version":3}
|
||||
1
test-wallets/wallet-test-life.json
Normal file
1
test-wallets/wallet-test-life.json
Normal file
@@ -0,0 +1 @@
|
||||
{"address":"17c5185167401ed00cf5f5b2fc97d9bbfdb7d025","id":"01234567-8901-4345-a789-012345678901","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"deadbeef1deadbeef2deadbeef301234"},"ciphertext":"cea502df4b9405fa2b6d8d19ec4e2953c5ce08e0e01d4c0292512ce62baef8f3","kdf":"scrypt","kdfparams":{"salt":"abcd1abcd2abcd3abcd4abcd5abcd6ef","n":1024,"dklen":32,"p":2,"r":4},"mac":"6e0a8e2409261d464e35a251f4b1fddd6ad0f7045a18d3957c127387f1c0de72"}}
|
||||
198
test.js
198
test.js
@@ -1,198 +0,0 @@
|
||||
// @TODO: Move to an actual unit test system
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
var ethereumTx = require('ethereumjs-tx');
|
||||
var ethereumUtil = require('ethereumjs-util');
|
||||
var iban = require('./node_modules/web3/lib/web3/iban.js');
|
||||
|
||||
var Wallet = require('./index.js');
|
||||
|
||||
function randomBytes(length) {
|
||||
return crypto.randomBytes(length);
|
||||
}
|
||||
|
||||
function randomHex(length) {
|
||||
if (length === 0) { return undefined; }
|
||||
var bytes = randomBytes(length);
|
||||
var hex = bytes.toString('hex');
|
||||
|
||||
if (length) {
|
||||
if (bytes[0] >= 128) {
|
||||
hex = hex.toUpperCase();
|
||||
} else {
|
||||
hex = hex.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return '0x' + hex;
|
||||
}
|
||||
|
||||
(function() {
|
||||
var abi = {
|
||||
"SimpleStorage": [
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[],
|
||||
"name":"getValue",
|
||||
"outputs":[{"name":"","type":"string"}],
|
||||
"type":"function"
|
||||
}, {
|
||||
"constant":false,
|
||||
"inputs":[{"name":"value","type":"string"}],
|
||||
"name":"setValue",
|
||||
"outputs":[],
|
||||
"type":"function"
|
||||
}, {
|
||||
"anonymous":false,
|
||||
"inputs":[
|
||||
{"indexed":false,"name":"oldValue","type":"string"},
|
||||
{"indexed":false,"name":"newValue","type":"string"}
|
||||
],
|
||||
"name":"valueChanged",
|
||||
"type":"event"
|
||||
}
|
||||
]
|
||||
}
|
||||
/*
|
||||
var privateKey = Wallet.utils.Buffer(32);
|
||||
privateKey.fill(0x42);
|
||||
|
||||
var wallet = new Wallet(privateKey);
|
||||
*/
|
||||
var contract = new Wallet._Contract(abi.SimpleStorage);
|
||||
var getValue = contract.getValue()
|
||||
var setValue = contract.setValue("foobar");
|
||||
var valueChanged = contract.valueChanged()
|
||||
console.log(getValue, setValue, valueChanged);
|
||||
})();
|
||||
|
||||
(function() {
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var privateKey = randomBytes(32);
|
||||
var official = '0x' + ethereumUtil.privateToAddress(privateKey).toString('hex');
|
||||
var ethers = (new Wallet(privateKey)).address;
|
||||
if (ethers !== ethereumUtil.toChecksumAddress(official)) {
|
||||
console.log(i);
|
||||
console.log('A', official);
|
||||
console.log('B', ethers);
|
||||
throw new Error('What?');
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
(function() {
|
||||
function testAddress(address) {
|
||||
var officialIban = (iban.fromAddress(address))._iban;
|
||||
|
||||
var ethersAddress = Wallet.getAddress(officialIban);
|
||||
var officialAddress = ethereumUtil.toChecksumAddress(address)
|
||||
|
||||
if (officialAddress !== ethersAddress) {
|
||||
console.log('A', officialAddress);
|
||||
console.log('B', ethersAddress);
|
||||
throw new Error('waht?! address');
|
||||
}
|
||||
|
||||
var ethersIban = Wallet.getIcapAddress(address);
|
||||
|
||||
if (officialIban !== ethersIban) {
|
||||
console.log('A', officialIban);
|
||||
console.log('B', ethersIban);
|
||||
throw new Error('waht?! icap');
|
||||
}
|
||||
}
|
||||
|
||||
testAddress('0x0000000000000000000000000000000000000000');
|
||||
testAddress('0xffffffffffffffffffffffffffffffffffffffff');
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
testAddress(randomHex(20));
|
||||
}
|
||||
})();
|
||||
|
||||
(function() {
|
||||
function testAddress(address) {
|
||||
var official = ethereumUtil.toChecksumAddress(address);
|
||||
var ethers = Wallet.getAddress(address);
|
||||
if (official !== ethers) {
|
||||
console.log('A', official);
|
||||
console.log('B', ethers);
|
||||
throw new Error('waht?!');
|
||||
}
|
||||
}
|
||||
testAddress('0x0000000000000000000000000000000000000000');
|
||||
testAddress('0xffffffffffffffffffffffffffffffffffffffff');
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
testAddress(randomHex(20));
|
||||
}
|
||||
})();
|
||||
|
||||
(function() {
|
||||
function testTransaction(privateKey, transaction, signature) {
|
||||
var rawTransaction = new ethereumTx(transaction);
|
||||
rawTransaction.sign(privateKey);
|
||||
var official = '0x' + rawTransaction.serialize().toString('hex');
|
||||
|
||||
var ethers = (new Wallet(privateKey)).sign(transaction);
|
||||
|
||||
if (ethers !== official) {
|
||||
console.log('A', ethers);
|
||||
console.log('B', official);
|
||||
throw new Error('What?');
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var transaction = {
|
||||
to: randomHex(20),
|
||||
data: randomHex(parseInt(10 * Math.random())),
|
||||
gasLimit: randomHex(parseInt(10 * Math.random())),
|
||||
gasPrice: randomHex(parseInt(10 * Math.random())),
|
||||
value: randomHex(parseInt(10 * Math.random())),
|
||||
nonce: randomHex(parseInt(10 * Math.random())),
|
||||
};
|
||||
|
||||
testTransaction(randomBytes(32), transaction);
|
||||
}
|
||||
|
||||
// See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/test/txs.json
|
||||
testTransaction(new Buffer('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex'), {
|
||||
nonce: "0x",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x2710",
|
||||
to: "0x0000000000000000000000000000000000000000",
|
||||
value: "0x",
|
||||
data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab",
|
||||
s: "0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13"
|
||||
});
|
||||
|
||||
testTransaction(new Buffer('e0a462586887362a18a318b128dbc1e3a0cae6d4b0739f5d0419ec25114bc722', 'hex'), {
|
||||
nonce: "0x06",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x01f4",
|
||||
to: "0xbe862ad9abfe6f22bcb087716c7d89a26051f74c",
|
||||
value: "0x016345785d8a0000",
|
||||
data: "0x",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x24a484bfa7380860e9fa0a9f5e4b64b985e860ca31abd36e66583f9030c2e29d",
|
||||
s: "0x4d5ef07d9e73fa2fbfdad059591b4f13d0aa79e7634a2bb00174c9200cabb04d"
|
||||
});
|
||||
|
||||
testTransaction(new Buffer('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex'), {
|
||||
nonce: "0x06",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x0974",
|
||||
to: "0xbe862ad9abfe6f22bcb087716c7d89a26051f74c",
|
||||
value: "0x016345785d8a0000",
|
||||
data: "0x00000000000000000000000000000000000000000000000000000000000000ad000000000000000000000000000000000000000000000000000000000000fafa0000000000000000000000000000000000000000000000000000000000000dfa0000000000000000000000000000000000000000000000000000000000000dfa00000000000000000000000000000000000000000000000000000000000000ad000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000df000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000df000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000d",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x5e9361ca27e14f3af0e6b28466406ad8be026d3b0f2ae56e3c064043fb73ec77",
|
||||
s: "0x29ae9893dac4f9afb1af743e25fbb6a63f7879a61437203cb48c997b0fcefc3a"
|
||||
});
|
||||
})();
|
||||
|
||||
44
tests/index.js
Normal file
44
tests/index.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
|
||||
console.log('Running test cases... (this can take a long time, please be patient)');
|
||||
|
||||
|
||||
// Test converting private keys to addresses
|
||||
module.exports.testPrivateKeyToAddress = require('./test-privatekey.js');
|
||||
|
||||
// Test checksum addresses
|
||||
module.exports.testChecksumAddress = require('./test-checksum-address.js');
|
||||
|
||||
// Test ICAP addresses
|
||||
module.exports.testIcapAddress = require('./test-icap-address.js');
|
||||
|
||||
|
||||
// Test ether strng formatting and parsing
|
||||
module.exports.testEtherFormat = require('./test-ether-format.js');
|
||||
|
||||
|
||||
// Test transactions
|
||||
module.exports.testTrasactions = require('./test-transactions.js');
|
||||
|
||||
|
||||
// Test Solidity encoding/decoding parameters
|
||||
module.exports.testSolidityCoder = require('./test-solidity-coder.js');
|
||||
|
||||
// Test the contract meta-class
|
||||
module.exports.testContracts = require('./test-contracts.js');
|
||||
|
||||
|
||||
// Test the secret storage JSON wallet encryption/decryption
|
||||
module.exports.testSecretStorage = require('./test-secret-storage.js');
|
||||
|
||||
// Test brain wallet generation
|
||||
module.exports.testBrainWallet = require('./test-brain-wallet.js');
|
||||
|
||||
|
||||
// Test contract address helper
|
||||
module.exports.testContractAddress = require('./test-contract-address.js');
|
||||
|
||||
|
||||
// Test the providers API (we still need to add a lot ore test cases here)
|
||||
module.exports.testProviders = require('./test-providers.js');
|
||||
|
||||
16
tests/test-brain-wallet.js
Normal file
16
tests/test-brain-wallet.js
Normal file
@@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
module.exports = function(test) {
|
||||
var username = new Wallet.utils.Buffer('ricmoo', 'utf8');
|
||||
var password = new Wallet.utils.Buffer('password', 'utf8');
|
||||
Wallet.summonBrainWallet(username, password).then(function(wallet) {
|
||||
test.equal(wallet.address, '0xbed9d2E41BdD066f702C4bDB86eB3A3740101acC', 'wrong wallet generated');
|
||||
test.done();
|
||||
}, function(error) {
|
||||
test.ok(false, 'Failed to generarte brain wallet');
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
27
tests/test-checksum-address.js
Normal file
27
tests/test-checksum-address.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var ethereumUtil = require('ethereumjs-util');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
|
||||
module.exports = function(test) {
|
||||
function testAddress(address) {
|
||||
var official = ethereumUtil.toChecksumAddress(address);
|
||||
var ethers = Wallet.getAddress(address);
|
||||
test.equal(ethers, official, 'wrong address');
|
||||
}
|
||||
|
||||
test.expect(2 + 10000);
|
||||
|
||||
testAddress('0x0000000000000000000000000000000000000000');
|
||||
testAddress('0xffffffffffffffffffffffffffffffffffffffff');
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
testAddress(utils.randomHexString(20));
|
||||
}
|
||||
test.done();
|
||||
};
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
21
tests/test-contract-address.js
Normal file
21
tests/test-contract-address.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
module.exports = function(test) {
|
||||
// Transaction: 0x939aa17985bc2a52a0c1cba9497ef09e092355a805a8150e30e24b753bac6864
|
||||
var transaction = {
|
||||
from: '0xb2682160c482eb985ec9f3e364eec0a904c44c23',
|
||||
nonce: 10,
|
||||
}
|
||||
|
||||
test.equal(
|
||||
Wallet.utils.getContractAddress(transaction),
|
||||
Wallet.getAddress('0x3474627d4f63a678266bc17171d87f8570936622'),
|
||||
'Failed to match contract address'
|
||||
)
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
87
tests/test-contracts.js
Normal file
87
tests/test-contracts.js
Normal file
@@ -0,0 +1,87 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
module.exports = function(test) {
|
||||
var contractAddress = '0xdfaf84077cF4bCECA4F79d167F47041Ed3006D5b';
|
||||
var contractABI = {
|
||||
"SimpleStorage": [
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[],
|
||||
"name":"getValue",
|
||||
"outputs":[{"name":"","type":"string"}],
|
||||
"type":"function"
|
||||
}, {
|
||||
"constant":false,
|
||||
"inputs":[{"name":"value","type":"string"}],
|
||||
"name":"setValue",
|
||||
"outputs":[],
|
||||
"type":"function"
|
||||
}, {
|
||||
"anonymous":false,
|
||||
"inputs":[
|
||||
{"indexed":false,"name":"oldValue","type":"string"},
|
||||
{"indexed":false,"name":"newValue","type":"string"}
|
||||
],
|
||||
"name":"valueChanged",
|
||||
"type":"event"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var contractInterface = new Wallet._Contract.Interface(contractABI.SimpleStorage);
|
||||
var getValue = contractInterface.getValue()
|
||||
var setValue = contractInterface.setValue("foobar");
|
||||
var valueChanged = contractInterface.valueChanged()
|
||||
|
||||
test.equal(getValue.data, '0x20965255', "wrong call data");
|
||||
test.equal(setValue.data, '0x93a0935200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000', "wrong transaction data");
|
||||
test.ok(
|
||||
(valueChanged.topics.length === 1 && valueChanged.topics[0] === '0x68ad6719a0070b3bb2f866fa0d46c8123b18cefe9b387ddb4feb6647ca418435'),
|
||||
"wrong call data"
|
||||
);
|
||||
|
||||
// @TODO - test decode
|
||||
|
||||
var privateKey = new Buffer(32);
|
||||
privateKey.fill(0x42);
|
||||
|
||||
var wallet = new Wallet(privateKey, 'http://localhost:8545');
|
||||
var contract = wallet.getContract(contractAddress, contractABI.SimpleStorage);
|
||||
|
||||
function testCall() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
contract.getValue().then(function(result) {
|
||||
test.equal(result, 'foobar', 'failed to call getVaue');
|
||||
resolve(result);
|
||||
}, function(error) {
|
||||
test.ok(false, 'failed to call getValue (is parity running on this host?)');
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function testEstimate() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
contract.estimate.setValue('foo').then(function(result) {
|
||||
test.equal(result.toString(16), '8b04', 'failed to estimate setVaue');
|
||||
resolve(result);
|
||||
}, function(error) {
|
||||
test.ok(false, 'failed to call getValue (is parity running on this host?)');
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
testCall(),
|
||||
testEstimate(),
|
||||
]).then(function(results) {
|
||||
test.done();
|
||||
}, function(error) {
|
||||
console.log('ERROR', error);
|
||||
test.done();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
60
tests/test-ether-format.js
Normal file
60
tests/test-ether-format.js
Normal file
@@ -0,0 +1,60 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
// @TODO: Add testcases where format receives hexidecimal string
|
||||
|
||||
var BN = Wallet.utils.BN;
|
||||
|
||||
module.exports = function(test) {
|
||||
function checkFormat(wei, targetEther, options) {
|
||||
var ether = Wallet.formatEther(wei, options);
|
||||
//console.log(wei, targetEther, options, ether);
|
||||
test.equal(ether, targetEther, 'Failed to match formatted ether');
|
||||
ether = ether.replace(/,/g, '');
|
||||
test.ok(Wallet.parseEther(ether).eq(wei), 'Failed to convert back to wei');
|
||||
}
|
||||
|
||||
function checkParse(ether, targetWei) {
|
||||
//console.log(ether, targetWei, Wallet.parseEther(ether));
|
||||
test.ok(targetWei.eq(Wallet.parseEther(ether)), 'Failed to match target wei');
|
||||
}
|
||||
|
||||
checkParse('123.012345678901234567', new BN('123012345678901234567'));
|
||||
|
||||
checkParse('1.0', new BN('1000000000000000000'));
|
||||
checkParse('1', new BN('1000000000000000000'));
|
||||
checkParse('1.00', new BN('1000000000000000000'));
|
||||
checkParse('01.0', new BN('1000000000000000000'));
|
||||
|
||||
checkParse('-1.0', new BN('-1000000000000000000'));
|
||||
|
||||
checkParse('0.1', new BN('100000000000000000'));
|
||||
checkParse('.1', new BN('100000000000000000'));
|
||||
checkParse('0.10', new BN('100000000000000000'));
|
||||
checkParse('.100', new BN('100000000000000000'));
|
||||
checkParse('00.100', new BN('100000000000000000'));
|
||||
|
||||
checkParse('-0.1', new BN('-100000000000000000'));
|
||||
|
||||
|
||||
checkFormat(new BN('10000000000000000'), '0.01');
|
||||
checkFormat(new BN('1000000000000000000'), '1.0');
|
||||
checkFormat(new BN('1230000000000000000'), '1.23');
|
||||
checkFormat(new BN('-1230000000000000000'), '-1.23');
|
||||
|
||||
checkFormat(new BN('1000000000000000000'), '1.000000000000000000', {pad: true});
|
||||
checkFormat(new BN('123000000000000000000'), '123.000000000000000000', {pad: true});
|
||||
checkFormat(new BN('1230000000000000000'), '1.230000000000000000', {pad: true});
|
||||
|
||||
checkFormat(new BN('-1230000000000000000'), '-1.230000000000000000', {pad: true});
|
||||
|
||||
checkFormat(new BN('1234567890000000000000000'), '1,234,567.89', {pad: false, commify: true});
|
||||
checkFormat(new BN('1234567890000000000000000'), '1,234,567.890000000000000000', {pad: true, commify: true});
|
||||
checkFormat(new BN('-1234567890000000000000000'), '-1,234,567.89', {pad: false, commify: true});
|
||||
|
||||
|
||||
test.done();
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
34
tests/test-icap-address.js
Normal file
34
tests/test-icap-address.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var ethereumUtil = require('ethereumjs-util');
|
||||
var iban = require('../node_modules/web3/lib/web3/iban.js');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
module.exports = function(test) {
|
||||
function testAddress(address) {
|
||||
var officialIban = (iban.fromAddress(address))._iban;
|
||||
|
||||
var ethersAddress = Wallet.getAddress(officialIban);
|
||||
var officialAddress = ethereumUtil.toChecksumAddress(address)
|
||||
|
||||
var ethersIban = Wallet.getIcapAddress(address);
|
||||
|
||||
test.equal(ethersAddress, officialAddress, 'wrong address');
|
||||
test.equal(ethersIban, officialIban, 'wrong ICAP address');
|
||||
}
|
||||
|
||||
test.expect(2 * (2 + 10000));
|
||||
|
||||
testAddress('0x0000000000000000000000000000000000000000');
|
||||
testAddress('0xffffffffffffffffffffffffffffffffffffffff');
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
testAddress(utils.randomHexString(20));
|
||||
}
|
||||
|
||||
test.done();
|
||||
};
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
19
tests/test-privatekey.js
Normal file
19
tests/test-privatekey.js
Normal file
@@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var ethereumUtil = require('ethereumjs-util');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
module.exports = function(test) {
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var privateKey = utils.randomBuffer(32);
|
||||
var ethereumLib = '0x' + ethereumUtil.privateToAddress(privateKey).toString('hex');
|
||||
var ethers = (new Wallet(privateKey)).address;
|
||||
test.equal(ethers, ethereumUtil.toChecksumAddress(ethereumLib), 'wrong address');
|
||||
}
|
||||
test.done();
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
105
tests/test-providers.js
Normal file
105
tests/test-providers.js
Normal file
@@ -0,0 +1,105 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var Web3 = require('web3');
|
||||
|
||||
// @TODO: We need to do a lot more test cases here:
|
||||
// - homestead
|
||||
// - sendTransaction
|
||||
// - estimateGas with various parameters set and not set
|
||||
// - estimateGas on a contract with from/value conditionals
|
||||
// - Metamask-style injected Web3
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
var url = 'http://localhost:8545';
|
||||
var web3Provider = new Web3.providers.HttpProvider(url)
|
||||
var web3 = new Web3(web3Provider)
|
||||
|
||||
var providers = [
|
||||
(new Wallet.providers.Web3Provider(web3Provider)),
|
||||
(new Wallet.providers.Web3Provider(web3)),
|
||||
(new Wallet.providers.HttpProvider(url)),
|
||||
(new Wallet.providers.EtherscanProvider({testnet: true})),
|
||||
]
|
||||
|
||||
var pending = [];
|
||||
|
||||
function checkMethod(method, params, expectedValue) {
|
||||
var checks = [];
|
||||
providers.forEach(function(provider) {
|
||||
checks.push(new Promise(function(resolve, reject) {
|
||||
provider[method].apply(provider, params).then(function(value) {
|
||||
resolve(value);
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
pending.push(new Promise(function(resolve, reject) {
|
||||
Promise.all(checks).then(function(results) {
|
||||
if (!expectedValue) { expectedValue = results[0]; }
|
||||
results.forEach(function(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) {
|
||||
console.log(error);
|
||||
test.ok(false, 'Error - ' + error.message)
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
checkMethod(
|
||||
'getBalance', ['0x7357589f8e367c2C31F51242fB77B350A11830F3']
|
||||
);
|
||||
checkMethod(
|
||||
'getTransactionCount', ['0x7357589f8e367c2C31F51242fB77B350A11830F3']
|
||||
);
|
||||
checkMethod('getGasPrice', []);
|
||||
checkMethod(
|
||||
'call', [{to: '0xdfaf84077cF4bCECA4F79d167F47041Ed3006D5b', data: '0x20965255'}],
|
||||
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000'
|
||||
);
|
||||
checkMethod(
|
||||
'estimateGas', [{
|
||||
to: '0xdfaf84077cF4bCECA4F79d167F47041Ed3006D5b',
|
||||
from: '0x7357589f8e367c2C31F51242fB77B350A11830F3',
|
||||
data: '0x93a0935200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'
|
||||
}],
|
||||
new Wallet.utils.BN('35588')
|
||||
);
|
||||
|
||||
var privateKey = new Buffer(32);
|
||||
privateKey.fill(0x42);
|
||||
|
||||
var wallet = new Wallet(privateKey, url);
|
||||
pending.push(new Promise(function(resolve, reject) {
|
||||
wallet.estimateGas({
|
||||
to: '0xdfaf84077cF4bCECA4F79d167F47041Ed3006D5b',
|
||||
data: '0x93a0935200000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003666f6f0000000000000000000000000000000000000000000000000000000000'
|
||||
}).then(function(value) {
|
||||
test.equal(value.toString(10), '35588', 'Failed to call wallet.estimateGas');
|
||||
resolve();
|
||||
}, function(error) {
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
|
||||
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;
|
||||
|
||||
135
tests/test-secret-storage.js
Normal file
135
tests/test-secret-storage.js
Normal file
@@ -0,0 +1,135 @@
|
||||
'use strict'
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
function equals(a, b) {
|
||||
if (typeof(a) !== typeof(b)) { return false; }
|
||||
if (Array.isArray(a)) {
|
||||
if (!Array.isArray(b)) { return false; }
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) { return false; }
|
||||
}
|
||||
} else if (typeof(a) === 'object') {
|
||||
if (!equals(Object.keys(a), Object.keys(b))) {
|
||||
return false;
|
||||
}
|
||||
for (var key in a) {
|
||||
if (!equals(a[key], b[key])) { return false; }
|
||||
}
|
||||
} else {
|
||||
return (a === b);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var crowdsale = [
|
||||
{
|
||||
address: '0x2e326fA404Fc3661de4F4361776ed9bBABDC26E3',
|
||||
data: fs.readFileSync('./test-wallets/wallet-test-encseed-foo.json').toString(),
|
||||
password: 'foo',
|
||||
privateKey: '0xcf367fc32bf789b3339c6664af4a12263e9db0e0eb70f247da1d1165e150c487',
|
||||
type: 'crowdsale'
|
||||
},
|
||||
{
|
||||
address: '0x0b88d4b324ec24C8c078551e6e5075547157E5b6',
|
||||
data: fs.readFileSync('./test-wallets/wallet-test-encseed-no-password.json').toString(),
|
||||
password: '',
|
||||
privateKey: '0xd4375d2a931db84ea8825b69a3128913597744d9236cacec675cc18e1bda4446',
|
||||
type: 'crowdsale'
|
||||
}
|
||||
];
|
||||
|
||||
var geth = [
|
||||
{
|
||||
address: '0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290',
|
||||
data: fs.readFileSync('./test-wallets/wallet-test-geth-foo.json').toString(),
|
||||
password: 'foo',
|
||||
privateKey: '0xf03e581353c794928373fb0893bc731aefc4c4e234e643f3a46998b03cd4d7c5',
|
||||
type: 'version3'
|
||||
},
|
||||
{
|
||||
address: '0x4A9cf99357F5789251a8D7FaD5b86D0F31EEB938',
|
||||
data: fs.readFileSync('./test-wallets/wallet-test-geth-no-password.json').toString(),
|
||||
password: '',
|
||||
privateKey: '0xa016182717223d01f776149ec0b4a217d0e9930cad263f205427c6d3cd5560e7',
|
||||
type: 'version3'
|
||||
},
|
||||
];
|
||||
|
||||
// Test crowdsale private key decryption
|
||||
crowdsale.forEach(function(testcase) {
|
||||
|
||||
// Check wallet type detection
|
||||
test.ok(Wallet.isCrowdsaleWallet(testcase.data), 'wrong wallet type detected');
|
||||
test.ok(!Wallet.isValidWallet(testcase.data), 'wrong wallet type detected');
|
||||
|
||||
var wallet = Wallet.decryptCrowdsale(testcase.data, testcase.password);
|
||||
|
||||
test.equal(wallet.privateKey, testcase.privateKey, 'wrong private key');
|
||||
test.equal(wallet.address, testcase.address, 'wrong address');
|
||||
});
|
||||
|
||||
var async = [];
|
||||
|
||||
geth.forEach(function(testcase) {
|
||||
// Check wallet type detection
|
||||
test.ok(Wallet.isValidWallet(testcase.data), 'wrong wallet type detected');
|
||||
test.ok(!Wallet.isCrowdsaleWallet(testcase.data), 'wrong wallet type detected');
|
||||
|
||||
async.push(new Promise(function(resolve, reject) {
|
||||
|
||||
// Test private key decryption
|
||||
var password = new Buffer(testcase.password, 'utf8');
|
||||
Wallet.decrypt(testcase.data, password).then(function(wallet) {
|
||||
test.equals(wallet.privateKey, testcase.privateKey, 'wrong private key')
|
||||
test.equals(wallet.address, testcase.address, 'wrong address');
|
||||
resolve();
|
||||
}, function(error) {
|
||||
console.log(error);
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
var privateKey = new Buffer(32);
|
||||
privateKey.fill(0x42);
|
||||
var password = new Buffer("foo", 'utf8');
|
||||
|
||||
async.push(new Promise(function(resolve, reject) {
|
||||
(new Wallet(privateKey)).encrypt(password, {
|
||||
scrypt: { N: (1 << 10), r: 4, p: 2 },
|
||||
iv: '0xdeadbeef1deadbeef2deadbeef301234',
|
||||
salt: '0xabcd1abcd2abcd3abcd4abcd5abcd6ef',
|
||||
uuid: '0x01234567890123456789012345678901',
|
||||
}).then(function(json) {
|
||||
var jsonWallet = fs.readFileSync('./test-wallets/wallet-test-life.json').toString();
|
||||
test.ok(equals(JSON.parse(json), JSON.parse(jsonWallet)), 'failed to encrypt wallet')
|
||||
//test.equal(json, jsonWallet, 'failed to encrypt wallet');
|
||||
Wallet.decrypt(json, password).then(function(wallet) {
|
||||
test.equal(wallet.privateKey, '0x' + privateKey.toString('hex'), 'decryption failed');
|
||||
resolve();
|
||||
}, function(error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}, function(error) {
|
||||
test.ok(false, 'callback returned error - ' + error.message);
|
||||
reject(error);
|
||||
});
|
||||
}));
|
||||
|
||||
Promise.all(async).then(function(results) {
|
||||
test.done();
|
||||
}, function(error) {
|
||||
console.log(error);
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
472
tests/test-solidity-coder.js
Normal file
472
tests/test-solidity-coder.js
Normal file
@@ -0,0 +1,472 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var solc = require('solc');
|
||||
var ethereumVm = require('ethereumjs-vm');
|
||||
var ethereumUtil = require('ethereumjs-util');
|
||||
|
||||
var BN = Wallet.utils.BN;
|
||||
var utils = require('./utils.js');
|
||||
var random = utils.random;
|
||||
|
||||
// Create the indent given a tabstop
|
||||
function indent(tabs) {
|
||||
var indent = new Buffer(tabs * 4);
|
||||
indent.fill(32);
|
||||
return indent.toString('utf8')
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
function createContractOutput(types, values) {
|
||||
var source = 'contract Test {\n';
|
||||
source += ' function test() constant returns (' + types.join(', ') + ') {\n';
|
||||
|
||||
var returns = [];
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
var name = String.fromCharCode(97 + i);
|
||||
|
||||
// Array type; do a deep copy
|
||||
if (types[i].indexOf('[') >= 0) {
|
||||
|
||||
// Each count (or optionally empty) array type
|
||||
var arrays = types[i].match(/\[[0-9]*\]/g);
|
||||
|
||||
// Allocate the space (only dynamic arrays require new)
|
||||
source += indent(2) + types[i] + ' memory ' + name;
|
||||
if (arrays[arrays.length - 1] === '[]') {
|
||||
source += ' = new ' + types[i] + '(' + values[i].length+ ')';
|
||||
}
|
||||
source +=';\n';
|
||||
|
||||
var baseType = types[i].substring(0, types[i].indexOf('['));
|
||||
|
||||
function recursiveSet(item, indices) {
|
||||
if (Array.isArray(item)) {
|
||||
item.forEach(function(item, index) {
|
||||
var i = indices.slice();
|
||||
i.unshift(index);
|
||||
recursiveSet(item, i);
|
||||
});
|
||||
} else {
|
||||
var loc = '';
|
||||
indices.forEach(function(index) {
|
||||
loc = '[' + index + ']' + loc;
|
||||
})
|
||||
|
||||
if (item instanceof BN) { item = item.toString(10); }
|
||||
source += indent(2) + name + loc + ' = ' + baseType + '(' + item + ');\n';
|
||||
}
|
||||
}
|
||||
recursiveSet(values[i], []);
|
||||
|
||||
// Dynamic type: bytes
|
||||
} else if (types[i] === 'bytes') {
|
||||
source += indent(2) + 'bytes memory ' + name + ' = new bytes(' + values[i].length + ');\n';
|
||||
source += indent(2) + 'assembly {\n'
|
||||
source += indent(3) + 'mstore(' + name + ', ' + values[i].length + ')\n';
|
||||
for (var j = 0; j < values[i].length; j++) {
|
||||
source += indent(3) + 'mstore8(add(' + name + ', ' + (32 + j) + '), ' + values[i][j] + ')\n';
|
||||
}
|
||||
source += indent(2) + '}\n'
|
||||
/*
|
||||
var value = '';
|
||||
for (var j = 0; j < values[i].length; j++) {
|
||||
value += '\\' + 'x' + values[i].slice(j, j + 1).toString('hex');
|
||||
}
|
||||
source += ' bytes memory ' + name + ' = "' + value + '";\n';
|
||||
*/
|
||||
|
||||
// Dynamic type: string
|
||||
} else if (types[i] === 'string') {
|
||||
source += ' string memory ' + name + ' = "' + values[i] + '";\n';
|
||||
|
||||
// Static type; just use the stack
|
||||
} else {
|
||||
source += ' ' + types[i] + ' ' + name + ' = ' + types[i] + '(' + values[i] + ');\n';
|
||||
}
|
||||
|
||||
// Track the name to return
|
||||
returns.push(name);
|
||||
}
|
||||
|
||||
// Return the values
|
||||
source += ' return (' + returns.join(', ') + ');\n';
|
||||
|
||||
source += ' }\n';
|
||||
source += '}\n';
|
||||
|
||||
try {
|
||||
var contract = solc.compile(source, 0);
|
||||
contract = contract.contracts.Test;
|
||||
contract.sourceCode= source;
|
||||
return contract;
|
||||
} catch (error) {
|
||||
console.log('Failed to compile ========');
|
||||
console.log({types: types, values: values, contract: contract});
|
||||
console.log(source);
|
||||
console.log('========');
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
function dumpHex(data) {
|
||||
for (var i = 2; i < data.length; i += 64) {
|
||||
console.log(' ' + data.substring(i, i + 64));
|
||||
}
|
||||
}
|
||||
|
||||
function recursiveEqual(a, b) {
|
||||
function fail() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof(a) === 'number') { a = new BN(a); }
|
||||
if (typeof(b) === 'number') { b = new BN(b); }
|
||||
if (utils.isHexString(a)) { a = utils.hexOrBuffer(a); }
|
||||
if (utils.isHexString(b)) { b = utils.hexOrBuffer(b); }
|
||||
|
||||
if (a.eq) {
|
||||
if (!b.eq || !a.eq(b)) { return fail(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(a)) {
|
||||
if (!Buffer.isBuffer(b) || Buffer.compare(a, b) !== 0) { return fail(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(a)) {
|
||||
if (!Array.isArray(b) || a.length !== b.length) { return fail(); }
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (!recursiveEqual(a[i], b[i])) { return fail(); }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a !== b) { return fail(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
var checkPromises = [];
|
||||
|
||||
var nextTestId = 0;
|
||||
var remaining = {};
|
||||
function check(types, values, normalizedValues) {
|
||||
if (!normalizedValues) { normalizedValues = values; }
|
||||
|
||||
// First make sure we agree with ourself
|
||||
var ethersData = Wallet._Contract.Interface.encodeParams(types, values);
|
||||
var ethersValues = Wallet._Contract.Interface.decodeParams(types, ethersData);
|
||||
|
||||
var okSelf = recursiveEqual(normalizedValues, ethersValues);
|
||||
test.ok(okSelf, "self encode/decode failed");
|
||||
if (!okSelf) {
|
||||
console.log('okSelf', okSelf, types, values, normalizedValues, ethersValues);
|
||||
}
|
||||
|
||||
checkPromises.push(new Promise(function(resolve, reject) {
|
||||
var testId = (nextTestId++);
|
||||
remaining[testId] = true;
|
||||
|
||||
// Use this when a contracts "hangs" (ie. 0-length arrays seem to hang the VM)
|
||||
//console.log('a', testId, types, values);
|
||||
|
||||
try {
|
||||
var contract = createContractOutput(types, values);
|
||||
var contractInterface = new Wallet._Contract.Interface(JSON.parse(contract.interface));
|
||||
var call = contractInterface.test.apply(contractInterface);
|
||||
var vm = new ethereumVm();
|
||||
vm.runCode({
|
||||
code: new Buffer(contract.runtimeBytecode, 'hex'),
|
||||
data: new Buffer(call.data.substring(2), 'hex'),
|
||||
gasLimit: '0x80000000'
|
||||
|
||||
}, function(error, result) {
|
||||
delete remaining[testId];
|
||||
// Use this when contract hangs (see the above try)
|
||||
//console.log('b', testId, Object.keys(remaining).join(','));
|
||||
|
||||
try {
|
||||
var vmData = '0x' + result.return.toString('hex');
|
||||
test.equal(ethersData, vmData, 'Failed to generate same output as VM');
|
||||
if (ethersData !== vmData) {
|
||||
console.log('\n\n');
|
||||
console.log(contract.sourceCode);
|
||||
console.log({
|
||||
types: types,
|
||||
values: values
|
||||
});
|
||||
console.log('ethers=');
|
||||
dumpHex(ethersData);
|
||||
console.log('vm=');
|
||||
dumpHex('0x' + result.return.toString('hex'));
|
||||
}
|
||||
|
||||
resolve();
|
||||
} catch(error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
} catch(error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
// Test cases: https://github.com/ethereum/solidity.js/blob/master/test/coder.decodeParam.js
|
||||
check(['int'], [new BN(1)]);
|
||||
check(['int'], [new BN(16)]);
|
||||
check(['int'], [new BN(-1)]);
|
||||
check(['int256'], [new BN(1)]);
|
||||
check(['int256'], [new BN(16)]);
|
||||
check(['int256'], [new BN(-1)]);
|
||||
check(['int8'], [new BN(16)]);
|
||||
check(['int32'], [new BN(16)]);
|
||||
check(['int64'], [new BN(16)]);
|
||||
check(['int128'], [new BN(16)]);
|
||||
|
||||
check(['uint'], [new BN(1)]);
|
||||
check(['uint'], [new BN(16)]);
|
||||
check(['uint'], [new BN(-1)], [new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)]);
|
||||
check(['uint256'], [new BN(1)]);
|
||||
check(['uint256'], [new BN(16)]);
|
||||
check(['uint256'], [new BN(-1)], [new BN('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)]);
|
||||
check(['uint8'], [new BN(16)]);
|
||||
check(['uint32'], [new BN(16)]);
|
||||
check(['uint64'], [new BN(16)]);
|
||||
check(['uint128'], [new BN(16)]);
|
||||
|
||||
check(['int', 'int'], [new BN(1), new BN(2)]);
|
||||
check(['int', 'int'], [new BN(1), new BN(2)]);
|
||||
check(['int[2]', 'int'], [[new BN(12), new BN(22)], new BN(3)]);
|
||||
check(['int[2]', 'int[]'], [[new BN(32), new BN(42)], [new BN(3), new BN(4), new BN(5)]]);
|
||||
|
||||
check(
|
||||
['bytes32'],
|
||||
['0x6761766f66796f726b0000000000000000000000000000000000000000000000']
|
||||
);
|
||||
check(
|
||||
['bytes'],
|
||||
[new Buffer('6761766f66796f726b', 'hex')]
|
||||
);
|
||||
|
||||
check(
|
||||
['string'],
|
||||
['\uD835\uDF63']
|
||||
);
|
||||
|
||||
check(
|
||||
['address', 'string', 'bytes6[4]', 'int'],
|
||||
[
|
||||
"0x97916ef549947a3e0d321485a31dd2715a97d455",
|
||||
"foobar2",
|
||||
["0xa165ab0173c6", "0xf0f37bee9244", "0xc8dc0bf08d2b", "0xc8dc0bf08d2b"],
|
||||
34
|
||||
]
|
||||
);
|
||||
|
||||
check(
|
||||
['bytes32'],
|
||||
['0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b']
|
||||
);
|
||||
check(
|
||||
['bytes'],
|
||||
[new Buffer('731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b')]
|
||||
);
|
||||
|
||||
check(
|
||||
['bytes32[2]'],
|
||||
[['0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b',
|
||||
'0x731a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b']]
|
||||
);
|
||||
|
||||
check(
|
||||
['bytes'],
|
||||
[new Buffer('131a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' +
|
||||
'231a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b' +
|
||||
'331a3afc00d1b1e3461b955e53fc866dcf303b3eb9f4c16f89e388930f48134b')]
|
||||
);
|
||||
|
||||
// Some extra checks for width and sign tests
|
||||
check(['uint32'], [14], [new BN(14)]);
|
||||
check(['uint32'], [14], [new BN(14)]);
|
||||
check(['uint32'], [-14], [new BN(0xfffffff2)]);
|
||||
check(['int32'], [14], [new BN(14)]);
|
||||
check(['int32'], [-14], [new BN(-14)]);
|
||||
|
||||
check(['int8'], [new BN(1)], [new BN(1)]);
|
||||
check(['int8'], [new BN(-1)], [new BN(-1)]);
|
||||
check(['int8'], [new BN(189)], [new BN(-67)]);
|
||||
check(['int8'], [new BN(-189)], [new BN(67)]);
|
||||
check(['int8'], [new BN(257)], [new BN(1)]);
|
||||
|
||||
check(['uint8'], [new BN(343)], [new BN(87)]);
|
||||
check(['uint8'], [new BN(-1)], [new BN(255)]);
|
||||
|
||||
check(['uint56[5]'], [[new BN(639), new BN(227), new BN(727), new BN(325), new BN(146)]]);
|
||||
|
||||
function randomTypeValue(onlyStatic) {
|
||||
switch (random(0, (onlyStatic ? 5: 8))) {
|
||||
case 0:
|
||||
var size = random(1, 33);
|
||||
return {
|
||||
type: 'bytes' + size,
|
||||
value: function() {
|
||||
var value = '0x' + utils.randomBuffer(size).toString('hex');
|
||||
return {
|
||||
value: value,
|
||||
normalized: value
|
||||
}
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
var signed = (random(0, 2) === 0);
|
||||
var type = (!signed ? 'u': '') + 'int';
|
||||
var size = 32;
|
||||
if (random(0, 4) > 0) {
|
||||
size = random(1, 33)
|
||||
type += (8 * size);
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
value: function() {
|
||||
var mask = '';
|
||||
for (var i = 0; i < size; i++) { mask += 'ff'; }
|
||||
|
||||
var value = random(-500, 1000);
|
||||
var normalized = (new BN(value)).toTwos(size * 8).and(new BN(mask, 16));
|
||||
if (signed) {
|
||||
normalized = normalized.fromTwos(size * 8);
|
||||
}
|
||||
return {
|
||||
value: value,
|
||||
normalized: normalized
|
||||
};
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
return {
|
||||
type: 'address',
|
||||
value: function() {
|
||||
var value = '0x' + utils.randomBuffer(20).toString('hex');
|
||||
return {
|
||||
value: value,
|
||||
normalized: value
|
||||
};
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
return {
|
||||
type: 'bool',
|
||||
value: function() {
|
||||
var value = (random(0, 2) === 0);
|
||||
return {
|
||||
value: value,
|
||||
normalized: value
|
||||
};
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
var size = random(1, 6); /// @TODO: Support random(0, 6)... Why is that even possible?
|
||||
var subTypeValue = randomTypeValue(true);
|
||||
return {
|
||||
type: subTypeValue.type + '[' + size + ']',
|
||||
value: function() {
|
||||
var values = [];
|
||||
var normalized = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
var value = subTypeValue.value();
|
||||
values.push(value.value);
|
||||
normalized.push(value.normalized);
|
||||
}
|
||||
return {
|
||||
value: values,
|
||||
normalized: normalized
|
||||
};
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
return {
|
||||
type: 'bytes',
|
||||
value: function() {
|
||||
var value = utils.randomBuffer(random(0, 100));
|
||||
return {
|
||||
value: value,
|
||||
normalized: value
|
||||
};
|
||||
},
|
||||
skip: 0
|
||||
}
|
||||
case 6:
|
||||
var text = 'abcdefghijklmnopqrstuvwxyz\u2014ABCDEFGHIJKLMNOPQRSTUVWXYZFOOBARfoobar'
|
||||
return {
|
||||
type: 'string',
|
||||
value: function() {
|
||||
var value = text.substring(0, random(0, 60));
|
||||
return {
|
||||
value: value,
|
||||
normalized: value
|
||||
};
|
||||
}
|
||||
}
|
||||
case 7:
|
||||
var size = random(1, 6); // @TODO: bug in solidity or VM prevents this from being 0
|
||||
var subTypeValue = randomTypeValue(true);
|
||||
return {
|
||||
type: subTypeValue.type + '[]',
|
||||
value: function() {
|
||||
var values = [];
|
||||
var normalized = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
var value = subTypeValue.value();
|
||||
values.push(value.value);
|
||||
normalized.push(value.normalized);
|
||||
}
|
||||
return {
|
||||
value: values,
|
||||
normalized: normalized
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @TODO: Test 0 arguments
|
||||
|
||||
// Create a bunch of random test cases
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var count = random(1, 4);
|
||||
var types = [], values = [], normalized = [];;
|
||||
for (var j = 0; j < count; j++) {
|
||||
var type = randomTypeValue();
|
||||
types.push(type.type);
|
||||
var value = type.value();
|
||||
values.push(value.value);
|
||||
normalized.push(value.normalized);
|
||||
}
|
||||
check(types, values, normalized);
|
||||
}
|
||||
|
||||
// Bug in solidity or in the VM, not sure, but this fails
|
||||
// check(['uint8[4][]'], [ [] ]);
|
||||
|
||||
Promise.all(checkPromises).then(function(results) {
|
||||
test.done();
|
||||
}, function(error) {
|
||||
console.log('ERROR', error);
|
||||
test.done();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
99
tests/test-transactions.js
Normal file
99
tests/test-transactions.js
Normal file
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
var Wallet = require('../index.js');
|
||||
|
||||
var ethereumTx = require('ethereumjs-tx');
|
||||
|
||||
var utils = require('./utils.js');
|
||||
|
||||
|
||||
function randomHexString(lowerRandomInterval, upperOpenInterval) {
|
||||
return utils.randomHexString(utils.random(lowerRandomInterval, upperOpenInterval));
|
||||
}
|
||||
|
||||
module.exports = function(test) {
|
||||
|
||||
function testTransaction(privateKey, transaction, signature) {
|
||||
var rawTransaction = new ethereumTx(transaction);
|
||||
rawTransaction.sign(privateKey);
|
||||
var ethereumLib = '0x' + rawTransaction.serialize().toString('hex');
|
||||
|
||||
var wallet = new Wallet(privateKey);
|
||||
var ethers = wallet.sign(transaction);
|
||||
|
||||
test.equal(ethers, ethereumLib, 'invalid transaction');
|
||||
|
||||
// @TODO: More testing on parsed transaction.
|
||||
test.equal(wallet.address, Wallet.parseTransaction(ethers).from, 'invalid parseTransaction');
|
||||
}
|
||||
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var transaction = {
|
||||
to: utils.randomHexString(20),
|
||||
data: randomHexString(0, 10),
|
||||
gasLimit: randomHexString(0, 10),
|
||||
gasPrice: randomHexString(0, 10),
|
||||
value: randomHexString(0, 10),
|
||||
nonce: randomHexString(0, 10),
|
||||
};
|
||||
|
||||
testTransaction(utils.randomBuffer(32), transaction);
|
||||
}
|
||||
|
||||
// See: https://github.com/ethereumjs/ethereumjs-tx/blob/master/test/txs.json
|
||||
testTransaction(new Buffer('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex'), {
|
||||
nonce: "0x",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x2710",
|
||||
to: "0x0000000000000000000000000000000000000000",
|
||||
value: "0x",
|
||||
data: "0x7f7465737432000000000000000000000000000000000000000000000000000000600057",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x5e1d3a76fbf824220eafc8c79ad578ad2b67d01b0c2425eb1f1347e8f50882ab",
|
||||
s: "0x5bd428537f05f9830e93792f90ea6a3e2d1ee84952dd96edbae9f658f831ab13"
|
||||
});
|
||||
|
||||
testTransaction(new Buffer('e0a462586887362a18a318b128dbc1e3a0cae6d4b0739f5d0419ec25114bc722', 'hex'), {
|
||||
nonce: "0x06",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x01f4",
|
||||
to: "0xbe862ad9abfe6f22bcb087716c7d89a26051f74c",
|
||||
value: "0x016345785d8a0000",
|
||||
data: "0x",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x24a484bfa7380860e9fa0a9f5e4b64b985e860ca31abd36e66583f9030c2e29d",
|
||||
s: "0x4d5ef07d9e73fa2fbfdad059591b4f13d0aa79e7634a2bb00174c9200cabb04d"
|
||||
});
|
||||
|
||||
testTransaction(new Buffer('164122e5d39e9814ca723a749253663bafb07f6af91704d9754c361eb315f0c1', 'hex'), {
|
||||
nonce: "0x06",
|
||||
gasPrice: "0x09184e72a000",
|
||||
gasLimit: "0x0974",
|
||||
to: "0xbe862ad9abfe6f22bcb087716c7d89a26051f74c",
|
||||
value: "0x016345785d8a0000",
|
||||
data: "0x00000000000000000000000000000000000000000000000000000000000000ad000000000000000000000000000000000000000000000000000000000000fafa0000000000000000000000000000000000000000000000000000000000000dfa0000000000000000000000000000000000000000000000000000000000000dfa00000000000000000000000000000000000000000000000000000000000000ad000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000df000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000df000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000d",
|
||||
}, {
|
||||
v: "0x1c",
|
||||
r: "0x5e9361ca27e14f3af0e6b28466406ad8be026d3b0f2ae56e3c064043fb73ec77",
|
||||
s: "0x29ae9893dac4f9afb1af743e25fbb6a63f7879a61437203cb48c997b0fcefc3a"
|
||||
});
|
||||
|
||||
// Test all possible blank fields
|
||||
var privateKey = new Buffer('0123456789012345678901234567890123456789012345678901234567890123', 'hex');
|
||||
for (var i = 0; i < 64; i++) {
|
||||
var transaction = {};
|
||||
if (i & (1 << 0)) { transaction.nonce = '0x02'; }
|
||||
if (i & (1 << 1)) { transaction.gasPrice = '0x03'; }
|
||||
if (i & (1 << 2)) { transaction.gasLimit = '0x04'; }
|
||||
if (i & (1 << 3)) { transaction.to = '0x0123456789012345678901234567890123456789'; }
|
||||
if (i & (1 << 4)) { transaction.value = '0x05'; }
|
||||
if (i & (1 << 5)) { transaction.data = '0x06'; }
|
||||
testTransaction(privateKey, transaction);
|
||||
}
|
||||
|
||||
test.done();
|
||||
};
|
||||
|
||||
module.exports.testSelf = module.exports;
|
||||
|
||||
25
tests/utils.js
Normal file
25
tests/utils.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var crypto = require('crypto');
|
||||
|
||||
var utils = require('../lib/utils.js');
|
||||
|
||||
function random(lowerRandomInterval, upperOpenInterval) {
|
||||
return lowerRandomInterval + parseInt((upperOpenInterval - lowerRandomInterval) * Math.random());
|
||||
}
|
||||
|
||||
function randomBuffer(length) {
|
||||
var buffer = crypto.randomBytes(length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function randomHexString(length) {
|
||||
return '0x' + randomBuffer(length).toString('hex');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
random: random,
|
||||
randomBuffer: randomBuffer,
|
||||
randomHexString: randomHexString,
|
||||
|
||||
isHexString: utils.isHexString,
|
||||
hexOrBuffer: utils.hexOrBuffer,
|
||||
}
|
||||
Reference in New Issue
Block a user